Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,35 @@ jobs:

- name: Run smoke test
run: python scripts/smoke_test.py

windows:
name: Windows Python 3.12
runs-on: windows-latest
# FlowGrid ledgers and repository fixtures are UTF-8. Python otherwise
# falls back to a Windows locale encoding for unqualified test file I/O.
env:
PYTHONUTF8: "1"

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip

- name: Install package and test runner
run: |
python -m pip install --upgrade pip
python -m pip install -e . pytest

- name: Run test suite
run: pytest -q

- name: Check current-state freshness
run: python scripts/check_current_state.py

- name: Run smoke test
run: python scripts/smoke_test.py
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ pip install -e .
pytest -q
```

FlowGrid ledgers and repository fixtures use UTF-8. Windows contributors should
run Python in UTF-8 mode so unqualified test-only `pathlib` reads and writes use
the same encoding contract:

```powershell
$env:PYTHONUTF8 = '1'
pytest -q
```

The FlowGrid runtime explicitly reads and writes project ledger files as UTF-8;
this environment setting is the repository test-process contract on Windows.

## Running the smoke test

```bash
Expand Down
22 changes: 22 additions & 0 deletions ITERATION_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,28 @@

---

## 2026-08-17:真实 Windows 兼容性报告回灌

### FLG-ITER-20260817-01:GBK 首次初始化、MSYS 路径和 Windows 测试链暴露兼容性缺口 [fixed — P0]

**场景**:一位外部测试者在 Windows 11、PowerShell 5.1 代码页 936、PortableGit Git Bash 和 Python 3.13.14 上,按项目提供的测试任务运行合并提交 `4cd893b`。测试使用真实营销讨论作为 raw session,原始报告和私有素材不进入公开仓库。

**观察**:`flg version`、smoke、`C:\` 和 `C:/` session 路径通过;pytest 为 `7 failed, 221 passed`。中文和空格目录已经完成文件写入,但 `init` 打印字面量 checkmark 时触发 GBK `UnicodeEncodeError`。Git Bash `/c/...` 目标没有经过已有路径归一化,被创建到 `C:\c\...`。

**逐项复核**:7 个 pytest failure 中,路径显示、`PYTHONPATH` 分隔符、manifest 路径和 Rich 表格宽度属于测试可移植性问题;`current-state` 分类器把 Windows `Path` 字符串与 Git 的 POSIX 路径直接比较,属于真实脚本兼容性缺陷。不能把 7 项统一归为测试错误。

**本地候选方向**:首次初始化成功标记改为 GBK 可编码 ASCII;`init --dir` 接入 `normalize_user_path`;修复跨平台断言和 current-state 路径分类;增加 Windows Python 3.12 CI。`doctor` 缺少 repo-map 当前只显示信息,不单独导致 strict 失败,本轮不改变这一产品语义。

**本地候选验证**:Windows 定向回归 `10 passed`,全量 `231 passed`,强制 repository source-tree smoke、current-state freshness、workflow YAML 解析与 `git diff --check` 均通过。Windows CI 已加入候选,尚未通过远端 runner 执行。

**首次 Windows CI**:`fef01e0` 的 Windows Python 3.12 job 为 `38 failed, 193 passed`。日志逐项显示其中 37 项来自测试和 fixture 的无编码 `pathlib` I/O 在 Windows 默认落到 `cp1252`,另 1 项来自 Rich 表格对 `pending_review` 的平台相关截断。后续候选把 UTF-8 明确为 Windows 测试进程契约,在 job 设置 `PYTHONUTF8=1`,并将 status 回归改为验证 pending warning 语义。

**第二次 Windows CI**:UTF-8 修复将结果收敛到 `1 failed, 230 passed`。残余失败揭示 BIZ 批量导入在 Windows 同一时钟刻度内生成相同 capture ID,后一条覆盖前一条。候选保留原有 ID 形态,改用独立 UUID 后缀,并增加冻结时钟下的唯一性回归。

**现场复测**:外部测试者在同一 Windows 11 环境特征下,以独立虚拟环境复测固定提交 `ed527d0abd442d53027100937295e5cd32690c8c`。PowerShell 5.1 代码页 936 初始化中文空格目录退出码为 0、无 `UnicodeEncodeError`;Git Bash `/c/Users/...` 正确落到 `C:\Users\...`,进入目标目录后 `flg status` 能识别项目。全量测试记录为 `232 passed / 0 failed / 0 errors`,smoke 通过。

**收口**:两个原始运行时缺陷已在报告主机和固定提交上闭环,PR #47 的 Linux 与 Windows CI 也全部通过。结论保持有界:这是单一外部 Windows 主机的直接修复证据,不代表全部 Windows 环境,也不推导用户满意度或产品采用情况。

## 2026-08-16:中文 owner 范围收拢与外部 Windows 案例收口

### FLG-ITER-20260816-01:中文 owner 范围收拢在短句与长口述中重复漏提取 [confirmed — P0]
Expand Down
35 changes: 34 additions & 1 deletion docs/product/current-state.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FlowGrid Current State

Last updated: 2026-08-12
Last updated: 2026-08-17
Primary branch: `master`
Current code version: `v0.3.0`
Current stage: `v0.4 core validation`
Expand Down Expand Up @@ -28,6 +28,39 @@ Its job is to let project state, boundaries, judgments, progress, and next actio

## Current Verification

- PR #46 merged feedback-driven continuity hardening into `master` at
`4cd893b`; Linux CI passed on Python 3.10, 3.11, and 3.12 with `228` tests.
- A 2026-08-17 external Windows 11 run against `4cd893b` passed installation,
version, smoke, and `C:\` / `C:/` session paths. It observed a GBK `init`
output crash, an MSYS `/c/` init target error, and seven pytest failures.
- The seven failures were reviewed individually: five are platform-sensitive
assertions, while two expose one real Windows path-normalization bug in the
current-state freshness classifier. See the
[bounded compatibility record](../../evals/results/windows-compatibility-observed-20260817.md).
- The current PR #47 candidate uses a GBK-safe init success marker, routes
`init --dir` through the shared MSYS path normalizer, makes the affected tests
platform-aware, and adds Windows Python 3.12 CI. GitHub Actions passes on
Python 3.10, 3.11, 3.12, and Windows Python 3.12.
- The first Windows CI run at `fef01e0` reported `38 failed, 193 passed`:
37 failures came from Python defaulting unqualified test and fixture I/O to
`cp1252`, and one came from Rich table-cell truncation. The follow-up candidate
documents UTF-8 as the Windows test-process contract, enables `PYTHONUTF8=1`
for the Windows job, and asserts status warning semantics independently of
table rendering.
- The UTF-8 follow-up reduced Windows CI to `1 failed, 230 passed`. The remaining
failure exposed a real same-timestamp capture-ID collision during a multi-item
BIZ import on Windows. The next candidate preserves the `cap-...-xxxxxx` ID
shape while replacing the timestamp-derived suffix with an independent UUID
suffix and adds a frozen-clock uniqueness regression.
- A same-host field rerun against fixed commit `ed527d0` then passed the two
original runtime scenarios: PowerShell 5.1 at code page 936 initialized a
Chinese, spaced path without an encoding crash, and Git Bash mapped an
explicit `/c/Users/...` target to `C:\Users\...` rather than
`C:\c\Users\...`. The rerun also reported `232` tests with zero failures
or errors and a passing repository smoke test. This closes the reported
defects for that host and fixed commit; it does not establish universal
Windows compatibility.

- PR #44 established the pre-Continuation V2 `master` baseline at merge commit
`11c337d`
- The independent FlowGrid AML Retriever ranked #8 in the first public Agent
Expand Down
7 changes: 7 additions & 0 deletions evals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ separately in [`case-registry.json`](case-registry.json). The registry records
both the supported claim and limitation of every case; it must not label
owner-operated evidence as independent external adoption.

The observed Windows 11 compatibility run against merge commit `4cd893b` and
the seven-failure disposition are recorded in
[`results/windows-compatibility-observed-20260817.md`](results/windows-compatibility-observed-20260817.md).
The source report used a real private transcript and remains outside the public
repository; the checked-in result keeps only the technical observations needed
to reproduce and verify the fixes.

## Evaluation Modes

Each scenario should support at least three modes:
Expand Down
106 changes: 106 additions & 0 deletions evals/results/windows-compatibility-observed-20260817.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Windows 11 compatibility observation and failure disposition

Date: 2026-08-17

Tested base: `4cd893b5b8198b7e74426fd0c3bde9e3d2e99b92`

## Evidence boundary

This record summarizes an externally executed Windows report supplied to the
project owner. The original report and its real marketing transcript remain
outside this repository. They establish observed behavior on one Windows host;
they do not establish user satisfaction or broad Windows compatibility. A
second report from the same host reran the bounded compatibility scenarios
against candidate commit `ed527d0abd442d53027100937295e5cd32690c8c`.

Observed environment:

- Windows 11 Pro, 64-bit, build 26200
- PowerShell 5.1 with console code page 936 (GBK)
- PortableGit Git Bash
- Python 3.13.14
- FlowGrid v0.3.0 at merge commit `4cd893b`

Observed baseline results:

- `flg version`: passed
- repository smoke test: passed
- full pytest: `7 failed, 221 passed`
- `C:\` and `C:/` session paths: passed
- Chinese and spaced project directory: files were created, then `init` exited
with `UnicodeEncodeError`
- Git Bash `/c/` init target: created under `C:\c\...` instead of the intended
drive root

## Runtime defects

| Observation | Assessment | Candidate action |
| --- | --- | --- |
| `init` prints a literal checkmark that code page 936 cannot encode | Runtime defect. The command fails after writing project files. | Use an ASCII first-run success marker and add a GBK-encodable output regression. |
| `init --dir /c/...` resolves to `C:\c\...` | Runtime defect. The existing MSYS normalizer was not used by `init`. | Route `--dir` through `normalize_user_path` and add an init wiring regression. |

## Seven pytest failures

The seven failures were reviewed individually. They do not share one cause.

| Reported failure | Disposition | Reason and action |
| --- | --- | --- |
| Windows path spelling expected `C:/...` but received `C:\...` | Test portability defect | Native `Path` display is platform-specific. Compare with `PureWindowsPath`. |
| Source-tree smoke `PYTHONPATH` expected `:` | Test portability defect | The implementation correctly uses `os.pathsep`; make the assertion do the same. |
| Forced-source smoke `PYTHONPATH` expected `:` | Test portability defect | Same path-separator issue as above. |
| Manifest metadata expected `/` separators | Test portability defect | Validate native `Path.parts` instead of a POSIX suffix string. |
| Status table expected the full `pending_review` cell | Rendering-sensitive test | Rich may truncate a cell at detected terminal width. Fix the test width while retaining the warning and lifecycle assertion. |
| Current-state classifier included its own state document in product drift | Script portability defect | `str(Path(...))` used backslashes on Windows while Git paths use `/`. Normalize Git-style paths in the classifier. |
| Current-state non-product case returned its own state document | Script portability defect | Same classifier defect as above. |

## Doctor boundary

The report attributed `doctor --strict` exit 1 to a missing
`.flg/repo-map.json`. Current FlowGrid semantics explicitly keep an unmapped
project compatible: no repo-map is displayed as informational and does not by
itself make strict mode fail. The report does not provide enough machine-readable
doctor output to identify another failing integrity item. This candidate does
not change doctor semantics.

## Local candidate verification

- focused Windows regressions: `10 passed`
- full repository suite: `231 passed`
- forced repository source-tree smoke: passed
- current-state freshness check: passed with the state document refresh
- workflow YAML parse and `git diff --check`: passed
- Windows Python 3.12 GitHub Actions job: added, not executed locally

The first remote Windows job at `fef01e0` produced `38 failed, 193 passed`.
Log review assigned 37 failures to unqualified test and fixture I/O using the
runner's `cp1252` default, plus one failure to Rich table-cell truncation. The
follow-up defines `PYTHONUTF8=1` as the Windows repository test-process contract
and changes the status regression to assert the warning behavior rather than a
fully rendered table cell.

That follow-up reduced the remote result to `1 failed, 230 passed`. The remaining
failure exposed a same-timestamp capture-ID collision in a two-item BIZ import
on Windows. The next candidate replaces the timestamp-derived six-character
suffix with an independent UUID suffix and adds a frozen-clock uniqueness test.

## Windows-host candidate rerun

On 2026-08-17, the external tester reran the bounded scenarios against fixed
candidate commit `ed527d0abd442d53027100937295e5cd32690c8c` in a fresh Python
3.13.14 virtual environment on the same Windows 11 host characteristics.

- PowerShell 5.1 at code page 936 initialized a Chinese, spaced project path
with exit code 0, no `UnicodeEncodeError`, and an intact `.flg` directory.
- Git Bash initialized an explicit `/c/Users/...` target under the intended
`C:\Users\...` path, not `C:\c\Users\...`; `flg status` then recognized
the project.
- The repository suite reported `232` tests, `0` failures, and `0` errors after
PortableGit was made available to subprocesses.
- The repository smoke test ended with `Smoke test passed.`
- `doctor --strict` still reported `Needs attention` for the temporary project
because no runtime identity was configured. That observation is outside the
two compatibility acceptance criteria and does not change doctor semantics.

This rerun is direct field evidence that the two reported runtime defects are
fixed for the tested host and commit. It remains one-host evidence, not a claim
of universal Windows compatibility.
4 changes: 3 additions & 1 deletion scripts/check_current_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ def _git(*args: str) -> str:


def product_changes_after_state_update(paths: list[str]) -> list[str]:
state_doc_path = STATE_DOC.as_posix()
return sorted(
path
for path in paths
if path != str(STATE_DOC) and path.startswith(PRODUCT_PREFIXES)
if path.replace("\\", "/") != state_doc_path
and path.replace("\\", "/").startswith(PRODUCT_PREFIXES)
)


Expand Down
7 changes: 5 additions & 2 deletions src/flg/commands/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

from __future__ import annotations

import hashlib
import json
import re
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional
Expand Down Expand Up @@ -40,7 +40,10 @@ def _ensure_captures_dir(root: Path) -> Path:
def _generate_id() -> str:
now = datetime.now()
ts = now.strftime("%Y%m%d-%H%M%S")
rand = hashlib.md5(str(now.timestamp()).encode()).hexdigest()[:6]
# Windows wall-clock resolution can return the same timestamp for multiple
# captures created in one import loop. Keep the public ID shape while using
# an independent random suffix so one judgment cannot overwrite another.
rand = uuid.uuid4().hex[:6]
return f"cap-{ts}-{rand}"


Expand Down
10 changes: 7 additions & 3 deletions src/flg/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from rich.table import Table
from rich.text import Text

from ..core.files import ensure_dir, is_flg_project, safe_write
from ..core.files import ensure_dir, is_flg_project, normalize_user_path, safe_write
from ..core.i18n import normalize_language
from ..core.state import create_initial_state, save_state
from .. import templates_en
Expand Down Expand Up @@ -67,7 +67,7 @@ def init_project(
This avoids the common pitfall of initializing in the wrong cwd.
"""
if directory:
root = Path(directory).expanduser().resolve()
root = normalize_user_path(directory).resolve()
root.mkdir(parents=True, exist_ok=True)
else:
root = Path.cwd()
Expand Down Expand Up @@ -252,7 +252,11 @@ def init_project(

# Display results
console.print()
console.print(f"[bold green]✓ FlowGrid project initialized: {project_name}[/bold green]")
# Keep first-run success output encodable by legacy Windows consoles such
# as code page 936. A literal checkmark crashes Rich's legacy renderer
# after the project has already been written, leaving users with a false
# failure and a partially confusing first-run state.
console.print(f"[bold green][OK] FlowGrid project initialized: {project_name}[/bold green]")
creation_path = Text("Created in: ", style="dim")
creation_path.append(str(root), style="dim")
console.print(creation_path, soft_wrap=True)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@

import json
import os
from datetime import datetime as RealDateTime

from typer.testing import CliRunner

from flg.cli import app
from flg.commands import capture as capture_command


runner = CliRunner()


def test_capture_ids_remain_unique_when_wall_clock_is_unchanged(monkeypatch):
class FrozenDateTime:
@classmethod
def now(cls):
return RealDateTime(2026, 8, 17, 14, 44, 8)

monkeypatch.setattr(capture_command, "datetime", FrozenDateTime)

assert capture_command._generate_id() != capture_command._generate_id()


def test_auto_confirm_keeps_inferred_capture_pending(tmp_path):
old_cwd = os.getcwd()
os.chdir(tmp_path)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import os
import os.path
import json
from pathlib import Path

from typer.testing import CliRunner

Expand Down Expand Up @@ -549,7 +550,7 @@ def test_continuity_manifest_is_compact_and_expands_by_decision_id(tmp_path):
assert "flg trace D-002" in manifest
assert "long rationale" not in manifest
assert ".flg/sessions" not in manifest
assert metadata["path"].endswith(".flg/context/manifest.md")
assert Path(metadata["path"]).parts[-3:] == (".flg", "context", "manifest.md")
finally:
os.chdir(old_cwd)

Expand Down
10 changes: 10 additions & 0 deletions tests/test_current_state_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,13 @@ def test_current_state_classifier_ignores_non_product_changes():
assert product_changes_after_state_update(
["README.md", "ITERATION_LOG.md", "docs/product/current-state.md"]
) == []


def test_current_state_classifier_normalizes_windows_git_paths():
assert product_changes_after_state_update(
[
"README.md",
"docs\\product\\current-state.md",
"src\\flg\\commands\\init.py",
]
) == ["src\\flg\\commands\\init.py"]
Loading
Loading