@namewta/speculo 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/template/.speculo/README.md +1 -1
  4. package/template/canonical/canonical-specdev-goal-plan.md +1 -1
  5. package/template/canonical/canonical-specdev-grill-with-docs.md +1 -1
  6. package/template/canonical/canonical-specdev-spec.md +1 -1
  7. package/template/canonical/canonical-specdev-tickets.md +1 -1
  8. package/template/canonical/canonical-specdev-wayfinder.md +1 -1
  9. package/template/workflows/learning/G-goal/G-goal.md +69 -0
  10. package/template/workflows/learning/G-goal/chain-template.md +26 -0
  11. package/template/workflows/learning/G-goal/coverage-matrix-template.md +40 -0
  12. package/template/workflows/learning/G-goal/goal-plan-template.md +281 -0
  13. package/template/workflows/learning/G-goal/orchestration-protocol.md +25 -0
  14. package/template/workflows/learning/G-goal/planning-modes.md +19 -0
  15. package/template/workflows/learning/G-goal/probe-template.md +43 -0
  16. package/template/workflows/learning/G-goal/progress-template.md +15 -0
  17. package/template/workflows/learning/G-goal/references/coverage-bar.md +27 -0
  18. package/template/workflows/learning/G-goal/references/external-goal-runner.md +43 -0
  19. package/template/workflows/learning/G-goal/references/stop-rules.md +22 -0
  20. package/template/workflows/learning/INDEX.md +2 -2
  21. package/template/workflows/learning/README.md +20 -5
  22. package/template/workflows/learning/common/rules/artifact-contract.md +1 -0
  23. package/template/workflows/learning/common/rules/questioning-policy.md +2 -0
  24. package/template/workflows/learning/common/rules/teaching-policy.md +1 -1
  25. package/template/workflows/learning/common/skills/socratic-questioning/SKILL.md +7 -7
  26. package/template/workflows/learning/common/tools/validate-learning.mjs +7 -1
  27. package/template/workflows/specdev/I-init-setup/I-init-setup.md +5 -1
  28. package/template/workflows/specdev/README.md +2 -2
  29. package/template/workflows/specdev/common/rules/activation-and-memory.md +1 -1
  30. package/template/workflows/specdev/common/rules/path-reference-contract.md +2 -0
  31. package/template/workflows/specdev/common/tools/validate-specdev.mjs +152 -6
package/README.md CHANGED
@@ -72,7 +72,7 @@ After initialization, the target project gains the following AI agent-callable a
72
72
 
73
73
  | Workflow | Work Entries | Description |
74
74
  |---|---:|---|
75
- | **learning** | 8 | Evidence-aware learning for projects, products, subjects, languages, and skills: complete 30–40 minute plain-language lessons, Socratic inquiry lessons, single-file homework review, optional retention review, and provenance-preserving topic synthesis |
75
+ | **learning** | 9 | Evidence-aware learning for projects, products, subjects, languages, and skills: complete 30–40 minute plain-language lessons, Socratic inquiry lessons, Goal-Plan compilation for later external /goal execution, single-file homework review, optional retention review, and provenance-preserving topic synthesis |
76
76
  | **specdev** | 14 | Local-first specification-driven development: archive, code review, diagnosis, grilling, implementation, setup, learning, goal planning, prototyping, architecture review, specs, tickets, triage, and wayfinding |
77
77
  | **ops** | 3 | Host inventory and project deployment: initialize, host manage, and APP/shared-service deploy with dual documentation |
78
78
  | **person** | 2 | Persona-methodology and rigorous deliberation workflows (Mao Zedong Cognitive OS; Bidirectional Steelman Deliberation) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namewta/speculo",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Workflow-packaged AI collaboration assets with state-safe refresh tooling.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "pretest": "pnpm build",
14
14
  "generate-canonical": "node scripts/generate-specdev-canonical.mjs",
15
15
  "validate-source-parity": "node scripts/check-specdev-source-parity.mjs",
16
- "validate-assets": "node scripts/check-specdev-source-parity.mjs && node scripts/generate-specdev-canonical.mjs --check && node scripts/validate-refresh-contracts.mjs && node scripts/validate-framework-assets.mjs && node scripts/check-template-links.mjs && node scripts/validate-workflow-disclosure.mjs && node template/workflows/specdev/common/tools/validate-specdev.mjs --self-check",
16
+ "validate-assets": "node scripts/check-specdev-source-parity.mjs && node scripts/generate-specdev-canonical.mjs --check && node scripts/validate-refresh-contracts.mjs && node scripts/validate-framework-assets.mjs && node scripts/check-template-links.mjs && node scripts/validate-workflow-disclosure.mjs && node template/workflows/specdev/common/tools/validate-specdev.mjs --self-check && node template/workflows/learning/common/tools/validate-learning.mjs --workflow-root template/workflows/learning",
17
17
  "check": "pnpm test && pnpm validate-assets",
18
18
  "verify-bin": "node scripts/verify-bin.mjs",
19
19
  "eval:scenarios": "node scripts/evaluate-scenarios.mjs",
@@ -14,7 +14,7 @@
14
14
 
15
15
  ## 读取顺序
16
16
 
17
- 1. 读取 `workspace.json`,以当前打开项目为 `project_root` 解析公共 roots
17
+ 1. 读取 `workspace.json`,以当前打开项目为 `project_root` 解析公共 roots。嵌套安装时,项目根 `.speculo/` 不是本目录;只有该文件声明的状态根是运行时状态的唯一持久化根,项目根 `.speculo/specdev` 非法。
18
18
  2. 从 `../workflows/<workflow>/INDEX.md` 发现 workflow并按需读取其中声明的永久知识;这一步不读取 Work 条目或运行状态。
19
19
  3. 用户明确激活 workflow 或 work 后,读取 INDEX 指向的 workflow 根 `README.md`,从其中的 Work 条目选择目标并读取具体入口文件。
20
20
  4. 按激活合同读取 `<Path>{roots.state}/{workflow}/status.json</Path>`。SpecDev/Learning 再读取当前 change `.status.json` 与 work 产物。Ops schema v3 读取 hosts/projects/deployments/allocations/bindings/releases 及对应运行记录,不创建 `changes/`。
@@ -1921,7 +1921,7 @@ node "${ZIP_SCRIPT}" "${RETURN_STAGING}" \
1921
1921
 
1922
1922
  ## Locate before read
1923
1923
 
1924
- 1. 先解析当前 workflow 的 roots、状态索引和稳定 ID;不存在时静默跳过,不能凭旧路径猜测。
1924
+ 1. 先打开 `workspace.json`,从中解析当前 workflow 的 roots、状态索引和稳定 ID;roots 必须来自该文件,不得凭字面猜测。不存在时静默跳过,不能凭旧路径猜测。
1925
1925
  2. 根据当前请求、Work 分支、关键词、稳定 ID、状态和 provenance,先搜索相关索引行或目录项,再定位最小相关 entry;不把索引全文默认装入上下文。
1926
1926
  3. 只回读命中的 entry 和直接 provenance;需要恢复、冲突裁决、归档、迁移或执行安全证明时,才读取该阶段声明的完整证据集合。
1927
1927
  4. 没有匹配证据时返回缺失证据并停止依赖该结论的分支,不补造事实。
@@ -982,7 +982,7 @@ Ticket 只有同时满足以下适用条件才可设置 `ready: true`:
982
982
 
983
983
  ## Locate before read
984
984
 
985
- 1. 先解析当前 workflow 的 roots、状态索引和稳定 ID;不存在时静默跳过,不能凭旧路径猜测。
985
+ 1. 先打开 `workspace.json`,从中解析当前 workflow 的 roots、状态索引和稳定 ID;roots 必须来自该文件,不得凭字面猜测。不存在时静默跳过,不能凭旧路径猜测。
986
986
  2. 根据当前请求、Work 分支、关键词、稳定 ID、状态和 provenance,先搜索相关索引行或目录项,再定位最小相关 entry;不把索引全文默认装入上下文。
987
987
  3. 只回读命中的 entry 和直接 provenance;需要恢复、冲突裁决、归档、迁移或执行安全证明时,才读取该阶段声明的完整证据集合。
988
988
  4. 没有匹配证据时返回缺失证据并停止依赖该结论的分支,不补造事实。
@@ -1072,7 +1072,7 @@ Direct Spec Evidence 至少包含:用户批准与轻量合同、Lead、实施
1072
1072
 
1073
1073
  ## Locate before read
1074
1074
 
1075
- 1. 先解析当前 workflow 的 roots、状态索引和稳定 ID;不存在时静默跳过,不能凭旧路径猜测。
1075
+ 1. 先打开 `workspace.json`,从中解析当前 workflow 的 roots、状态索引和稳定 ID;roots 必须来自该文件,不得凭字面猜测。不存在时静默跳过,不能凭旧路径猜测。
1076
1076
  2. 根据当前请求、Work 分支、关键词、稳定 ID、状态和 provenance,先搜索相关索引行或目录项,再定位最小相关 entry;不把索引全文默认装入上下文。
1077
1077
  3. 只回读命中的 entry 和直接 provenance;需要恢复、冲突裁决、归档、迁移或执行安全证明时,才读取该阶段声明的完整证据集合。
1078
1078
  4. 没有匹配证据时返回缺失证据并停止依赖该结论的分支,不补造事实。
@@ -1867,7 +1867,7 @@ implementation owner 只在来源 worktree 修改授权项目路径,运行 Tic
1867
1867
 
1868
1868
  ## Locate before read
1869
1869
 
1870
- 1. 先解析当前 workflow 的 roots、状态索引和稳定 ID;不存在时静默跳过,不能凭旧路径猜测。
1870
+ 1. 先打开 `workspace.json`,从中解析当前 workflow 的 roots、状态索引和稳定 ID;roots 必须来自该文件,不得凭字面猜测。不存在时静默跳过,不能凭旧路径猜测。
1871
1871
  2. 根据当前请求、Work 分支、关键词、稳定 ID、状态和 provenance,先搜索相关索引行或目录项,再定位最小相关 entry;不把索引全文默认装入上下文。
1872
1872
  3. 只回读命中的 entry 和直接 provenance;需要恢复、冲突裁决、归档、迁移或执行安全证明时,才读取该阶段声明的完整证据集合。
1873
1873
  4. 没有匹配证据时返回缺失证据并停止依赖该结论的分支,不补造事实。
@@ -650,7 +650,7 @@ resolution: answered
650
650
 
651
651
  ## Locate before read
652
652
 
653
- 1. 先解析当前 workflow 的 roots、状态索引和稳定 ID;不存在时静默跳过,不能凭旧路径猜测。
653
+ 1. 先打开 `workspace.json`,从中解析当前 workflow 的 roots、状态索引和稳定 ID;roots 必须来自该文件,不得凭字面猜测。不存在时静默跳过,不能凭旧路径猜测。
654
654
  2. 根据当前请求、Work 分支、关键词、稳定 ID、状态和 provenance,先搜索相关索引行或目录项,再定位最小相关 entry;不把索引全文默认装入上下文。
655
655
  3. 只回读命中的 entry 和直接 provenance;需要恢复、冲突裁决、归档、迁移或执行安全证明时,才读取该阶段声明的完整证据集合。
656
656
  4. 没有匹配证据时返回缺失证据并停止依赖该结论的分支,不补造事实。
@@ -0,0 +1,69 @@
1
+ ---
2
+ id: learning/goal
3
+ type: workflow-entry
4
+ workflow: learning
5
+ name: 目标学习(目标模式)
6
+ description: 为选定编程项目编译一份可被外部 /goal 执行的完整 Goal-Plan;计划会话可跟随 A 写出课程地图,但不写 Lesson、不向学习者提问、不自动串联 H/R/C。
7
+ keywords: [goal, 目标模式, 目标学习, chain, coverage, mine, plan]
8
+ ---
9
+
10
+ # 目标学习(目标模式)
11
+
12
+ > 激活本 Work 后,先读取 `<Path>{roots.workflows}/learning/README.md</Path>`。
13
+
14
+ G-goal 是计划编译器,不是授课编排器。激活后写出完整 Goal-Plan 并停止。授课、挖掘与验收由用户在 AI CLI 中 `/goal` 读取该计划执行。
15
+
16
+ ## 读取范围
17
+
18
+ 1. 先读取 `<Path>{roots.workflows}/learning/README.md</Path>` 与当前 Work 的状态入口。
19
+ 2. 再读取 `<Path>{roots.workflows}/learning/common/rules/activation-and-memory.md</Path>`,按当前 Change、项目路径和关键词定位最小相关工件。
20
+ 3. 只在本 Work 明确要求恢复、冲突、执行安全、创建 Change 或归档证据时扩展为全量读取;缺少匹配证据或 owner/gateway 时停止受影响分支。
21
+
22
+ ## 模式
23
+
24
+ | 用户意图 | 模式 | 谁执行 | 必须按需读取 |
25
+ | --- | --- | --- | --- |
26
+ | 选定项目并生成完整计划(默认) | `plan` | 本 Work | `<Path>{roots.workflows}/learning/G-goal/planning-modes.md</Path>`、`<Path>{roots.workflows}/learning/G-goal/goal-plan-template.md</Path>`、`<Path>{roots.workflows}/learning/A-assess-and-plan/A-assess-and-plan.md</Path>` |
27
+ | 源码或范围变化,重编译计划 | `replan` | 本 Work | 同上,加 `<Path>{roots.workflows}/learning/G-goal/orchestration-protocol.md</Path>`;旧 `goal-plan.md` 只读归档到 `goal/revisions/` |
28
+ | 按计划写课与挖掘 | `run` / `resume` | 外部 `/goal` | 计划正文 §5 / §8;本 Work 不在激活会话内执行 |
29
+ | 对照矩阵验收 | `verify` | 外部 `/goal`,或以后再激活本 Work 只读验收 | 计划正文 §6 与 `<Path>{roots.workflows}/learning/G-goal/references/stop-rules.md</Path>` |
30
+
31
+ `plan` 写出的文档不构成执行授权。`ready_for_execution: false`,直到用户把计划交给 `/goal`。
32
+
33
+ ## 流程
34
+
35
+ 1. 解析 roots 与 Learning v2 状态。roots 必须来自已打开的 `<Path>{roots.state}/workspace.json</Path>`。若尚无 `status.json` / `locations.json`,先按 `<Path>{roots.workflows}/learning/I-init-setup/I-init-setup.md</Path>` 写入空状态骨架,不创建知识条目。
36
+ 2. 范围访谈一次只问一个缺口:项目路径、in-scope 模块/公开 API、out-of-scope、`expression_level`、`coverage_depth`。用户已答过不重复问。
37
+ 3. 创建或恢复 `YYYY-MM-DD-<kebab-topic>[-NN]` Change,生成 `.status.json`,设置 `phase=planning`、`current_work=learning/goal`。
38
+ 4. 读取目标仓库,做编程库存:C4 Context + Container、公开入口、主数据存储、一条主路径与一条失败路径、按模块归组的范围内函数。琐碎 helper、生成代码、测试夹具标 `covered-by-parent` 或 `deferred`,不为它们开课。
39
+ 5. 若本 Change 尚无可用 `course.md`,跟随 `<Path>{roots.workflows}/learning/A-assess-and-plan/A-assess-and-plan.md</Path>` 写出 `course.md`、`background/foundation.md`、`baseline.md`、`sources.md` 和 Change `INDEX.md`。这些文件仍归 A 所有;本 Work 只在编译计划时走 A 合同。已有课程地图则核对,不默默重写。
40
+ 6. 按模板写出完整计划,禁止留下「待调研」章节:
41
+ - `<Path>{roots.state}/learning/changes/{change}/goal/goal-plan.md</Path>`
42
+ - `<Path>{roots.state}/learning/changes/{change}/goal/chain.md</Path>`
43
+ - `<Path>{roots.state}/learning/changes/{change}/goal/coverage-matrix.md</Path>`
44
+ - `<Path>{roots.state}/learning/changes/{change}/goal/progress.md</Path>`(检查点骨架)
45
+ 7. 计划必须内嵌可粘贴的 `/goal` 启动块,并列出最小读取清单。覆盖规则读 `<Path>{roots.workflows}/learning/G-goal/references/coverage-bar.md</Path>`;停止规则读 `<Path>{roots.workflows}/learning/G-goal/references/stop-rules.md</Path>`;执行器合同读 `<Path>{roots.workflows}/learning/G-goal/references/external-goal-runner.md</Path>`。
46
+ 8. 设置 `ready_for_execution: false`。更新 `learning-log.md` 与 Change `.status.json`:`works_run` 追加 `learning/goal`,清空 `current_work`。向用户打印 Goal-Plan 路径和 `/goal` 粘贴块。
47
+ 9. 停止。不写 `lessons/`,不写 `goal/probes/`,不创建 `inquiry/` 批次,不自动激活 L/H/R/C/A-archive,不写 mastered。
48
+
49
+ ## 完成标准
50
+
51
+ - `goal/goal-plan.md` 按模板填实 Outcome、范围、四轴合同、Chain、Wave、派单菜谱、授权矩阵、DoD、HARD NO、Resume 和最小读取清单;
52
+ - `course.md` 有可观察 OBJ;`chain.md` 与矩阵使用库存得到的真实模块名,不是占位符;
53
+ - 计划会话结束时 `lessons/` 仍无讲义正文(只允许空 INDEX);
54
+ - 粘贴块含 Outcome、verification surface、constraints、boundaries、iteration policy、blocked-stop;
55
+ - 文档中的「允许」不构成 `/goal` 之外的额外授权;
56
+ - 不复活 `Q-quiz`,不把 mine 写成学习者问答课。
57
+
58
+ ## 子文件
59
+
60
+ - 计划模板:`<Path>{roots.workflows}/learning/G-goal/goal-plan-template.md</Path>`
61
+ - Chain 模板:`<Path>{roots.workflows}/learning/G-goal/chain-template.md</Path>`
62
+ - 覆盖矩阵模板:`<Path>{roots.workflows}/learning/G-goal/coverage-matrix-template.md</Path>`
63
+ - 挖掘模板:`<Path>{roots.workflows}/learning/G-goal/probe-template.md</Path>`
64
+ - 进度模板:`<Path>{roots.workflows}/learning/G-goal/progress-template.md</Path>`
65
+ - 模式:`<Path>{roots.workflows}/learning/G-goal/planning-modes.md</Path>`
66
+ - 编排:`<Path>{roots.workflows}/learning/G-goal/orchestration-protocol.md</Path>`
67
+ - 覆盖尺:`<Path>{roots.workflows}/learning/G-goal/references/coverage-bar.md</Path>`
68
+ - 停止规则:`<Path>{roots.workflows}/learning/G-goal/references/stop-rules.md</Path>`
69
+ - 外部执行器:`<Path>{roots.workflows}/learning/G-goal/references/external-goal-runner.md</Path>`
@@ -0,0 +1,26 @@
1
+ # Chain: <主题>
2
+
3
+ 本文件是 Goal 的课程序列投影,不是 C-consolidate,不搬迁 Change。权威 OBJ 仍在 `course.md`。
4
+
5
+ ## DAG
6
+
7
+ ```text
8
+ L-001 → L-002
9
+ ```
10
+
11
+ ## Lessons
12
+
13
+ | ID | 标题 | OBJ | 覆盖格子 | 前置 | 估时 | 将写入 | 状态 |
14
+ | --- | --- | --- | --- | --- | --- | --- | --- |
15
+ | L-001 | <系统地图> | OBJ-01 | C:context, D:happy-path | — | 35 | `lessons/L-001-<slug>.md` | planned |
16
+
17
+ 状态:`planned` | `writing` | `written` | `mined` | `revised` | `deferred`。
18
+
19
+ ## Waves
20
+
21
+ | Wave | 焦点 | Lessons | 上限 | Gate |
22
+ | --- | --- | --- | --- | --- |
23
+ | 1 | C4 Context + Container + 主数据流 | L-001 | ≤8 或 1 个容器 | G2/G3 |
24
+ | 2 | 范围内公开 API 与方法性状 | | ≤8 或 1 个容器 | G2/G3 |
25
+
26
+ 不改变任何覆盖格子的节点不要写入本表。
@@ -0,0 +1,40 @@
1
+ # Coverage Matrix: <主题>
2
+
3
+ 状态只允许:`uncovered` | `covered` | `deferred(reason)` | `covered-by-parent`。
4
+
5
+ 默认档:架构/数据流 = Relational;范围内函数/方法 = Unistructural + explained。
6
+
7
+ ## (c) 业务架构
8
+
9
+ | 单元 | 类型 | 状态 | 证据 | 备注 |
10
+ | --- | --- | --- | --- | --- |
11
+ | <系统 Context> | Context | uncovered | | |
12
+ | <容器 A> | Container | uncovered | | |
13
+
14
+ ## (d) 数据流
15
+
16
+ | 路径 | 源 → 汇 | 状态 | 证据 | 备注 |
17
+ | --- | --- | --- | --- | --- |
18
+ | 主路径 | <input> → <store> | uncovered | | |
19
+ | 失败路径 | <input> → <error> | uncovered | | |
20
+
21
+ ## (a) 函数目的
22
+
23
+ | 符号 | 模块 | 状态 | 证据 | 备注 |
24
+ | --- | --- | --- | --- | --- |
25
+ | <exportedFn> | <module> | uncovered | | |
26
+ | <helper> | <module> | covered-by-parent | | 由父模块课覆盖 |
27
+
28
+ ## (b) 方法性状
29
+
30
+ | 符号 | 性状 | 状态 | 证据 | 备注 |
31
+ | --- | --- | --- | --- | --- |
32
+ | <exportedFn> | 副作用/失败路径 | uncovered | | |
33
+
34
+ ## 闭合统计
35
+
36
+ - in-scope 格子总数:
37
+ - covered:
38
+ - deferred:
39
+ - covered-by-parent:
40
+ - uncovered:
@@ -0,0 +1,281 @@
1
+ ---
2
+ schema_version: 1
3
+ artifact: learning-goal-plan
4
+ change: YYYY-MM-DD-<kebab-topic>
5
+ topic: <topic-id>
6
+ project_path: <path-to-source-repo>
7
+ status: draft
8
+ modes: [plan]
9
+ orchestration: external-goal
10
+ coverage_domain: programming
11
+ expression_level: plain
12
+ coverage_depth: standard
13
+ wave_cap: 8
14
+ max_waves: 2
15
+ ready_for_execution: false
16
+ ---
17
+
18
+ # Goal-Plan: <主题>
19
+
20
+ 本文件由 G-goal 在 `plan` / `replan` 会话编译。计划正文里的「允许」不构成执行授权。`ready_for_execution: false`,直到用户把本计划交给外部 AI CLI `/goal`。
21
+
22
+ ## 路径
23
+
24
+ - 本计划:`<Path>{roots.state}/learning/changes/{change}/goal/goal-plan.md</Path>`
25
+ - Chain:`<Path>{roots.state}/learning/changes/{change}/goal/chain.md</Path>`
26
+ - 覆盖矩阵:`<Path>{roots.state}/learning/changes/{change}/goal/coverage-matrix.md</Path>`
27
+ - 进度:`<Path>{roots.state}/learning/changes/{change}/goal/progress.md</Path>`
28
+ - 挖掘探针:`<Path>{roots.state}/learning/changes/{change}/goal/probes/</Path>`
29
+ - 验收:`<Path>{roots.state}/learning/changes/{change}/goal/verify.md</Path>`
30
+ - 旧计划:`<Path>{roots.state}/learning/changes/{change}/goal/revisions/</Path>`
31
+ - 课程地图(A 所有):`<Path>{roots.state}/learning/changes/{change}/course.md</Path>`
32
+ - 讲义(L 所有):`<Path>{roots.state}/learning/changes/{change}/lessons/</Path>`
33
+ - 问答课(Q 所有,`/goal` 不得写入):`<Path>{roots.state}/learning/changes/{change}/inquiry/</Path>`
34
+
35
+ ---
36
+
37
+ ## §0 `/goal` 粘贴块
38
+
39
+ 把下面整块复制到 AI CLI 后发送 `/goal`。不要改写 Outcome 或 HARD NO。
40
+
41
+ ```text
42
+ /goal READ <Path>{roots.state}/learning/changes/{change}/goal/goal-plan.md</Path>
43
+
44
+ Outcome:
45
+ 范围内覆盖矩阵格子全部变为 covered | deferred(reason) | covered-by-parent;
46
+ 每个 chain 节点有一份 L 合同 Lesson;
47
+ 每节新课或改写课有对应 goal/probes/GP-*.md;
48
+ 写好 goal/verify.md。
49
+
50
+ Verification surface:
51
+ goal/coverage-matrix.md 状态列;
52
+ lessons/L-*.md 存在且满足 L-lesson 合同;
53
+ goal/probes/GP-*.md 关闭态且至少改变过一个矩阵格子,或显式 defer;
54
+ goal/verify.md 对照 stop-rules 逐条勾选。
55
+
56
+ Constraints:
57
+ roots 只来自已打开的 workspace.json;
58
+ 只写当前 Change 的 lessons/ 与 goal/;
59
+ 派单跟随 L-lesson 与 socratic-questioning audience=mine;
60
+ 不发明库存里没有的函数。
61
+
62
+ Boundaries:
63
+ 不写 inquiry/、homework/、review/、synthesis/、archive/;
64
+ 不写 mastered;
65
+ 不复活 Q-quiz;
66
+ 不把 mine 写成学习者问答课;
67
+ 不自动激活 H/R/C/A-archive。
68
+
69
+ Iteration policy:
70
+ 同一时间只推进一个 chain 节点;
71
+ 先 L 写课,再 mine 探针,再更新矩阵与 progress;
72
+ 缺口可 re-dispatch-L 或 defer(reason),不得用新问句代替矩阵变化。
73
+
74
+ Blocked-stop:
75
+ 本波矩阵无变化;达到 wave_cap 或 max_waves;用户暂停;
76
+ 缺源码 / 缺 baseline / 目标无法核对;所有权冲突或路径越界。
77
+ ```
78
+
79
+ 最小读取顺序见 §11。
80
+
81
+ ---
82
+
83
+ ## §1 Outcome and Authority
84
+
85
+ ### Outcome
86
+
87
+ 合格读者读完本 Goal 后,能用范围内源码与已写成的 Lesson 说明:系统 Context 与 Container、一条主路径与一条失败路径、范围内公开/领域函数的目的,以及公开 API / 枢纽方法的副作用与失败路径。完成证据是矩阵闭合 + Lesson + probe + `goal/verify.md`,不是「课写完」。
88
+
89
+ ### Success and False Completion
90
+
91
+ 算完成:stop-rules 的「完成即停」全部满足。
92
+
93
+ 不算完成:
94
+
95
+ - 课写完
96
+ - 文件巡览结束
97
+ - 模型表示「已经理解」
98
+ - 主观百分比
99
+ - 只生成了 Goal-Plan 本身
100
+
101
+ ### Non-goals
102
+
103
+ - 不自动激活 `H-homework`、`R-review`、`C-consolidate`、`A-archive`
104
+ - 不写 mastered
105
+ - 不复活 `Q-quiz`
106
+ - 不使用 `Q-question` 的 `inquiry/` Response 协议
107
+ - 不为琐碎 helper、生成代码、测试夹具单独开课
108
+
109
+ ### Authoritative Inputs
110
+
111
+ | 优先级 | 输入 | 用途 |
112
+ | --- | --- | --- |
113
+ | 1 | 用户当场决定 | 覆盖范围、暂停、defer |
114
+ | 2 | `course.md` OBJ | 权威学习目标;本文件不改写 OBJ |
115
+ | 3 | `goal/coverage-matrix.md` | 格子状态是覆盖权威 |
116
+ | 4 | `goal/chain.md` | 课程序列投影 |
117
+ | 5 | 本计划派单菜谱 | `/goal` 只按 §5 / §7 行动 |
118
+ | 6 | 源码事实 | 库存与证据;不得发明符号 |
119
+
120
+ ---
121
+
122
+ ## §2 Scope
123
+
124
+ ### In-scope
125
+
126
+ - 项目路径:
127
+ - 模块 / 公开 API:
128
+ - 必须覆盖的容器:
129
+
130
+ ### Out-of-scope
131
+
132
+ -
133
+
134
+ ### covered-by-parent / deferred
135
+
136
+ - 琐碎 helper、生成代码、测试夹具:`covered-by-parent` 或 `deferred(reason)`
137
+ - 「每一个函数」= 范围内公开/领域函数,不是仓库里每一个私有 one-liner
138
+ - 不改变任何覆盖格子的 Lesson 或 probe 不要写
139
+
140
+ ### C4 库存快照
141
+
142
+ ```text
143
+ [Context]
144
+ <system> --uses--> <actor / external>
145
+
146
+ [Container]
147
+ <container-a>
148
+ <container-b>
149
+
150
+ [Happy path]
151
+ <input> -> <transform> -> <store>
152
+
153
+ [Fail path]
154
+ <input> -> <error>
155
+ ```
156
+
157
+ 范围内函数按模块归组,清单写在 `goal/coverage-matrix.md` 的 (a)(b) 表,不在本节约维护第二份过期名单。
158
+
159
+ ---
160
+
161
+ ## §3 Coverage Contract
162
+
163
+ v1 只实现编程四轴。默认档与证据规则以 `<Path>{roots.workflows}/learning/G-goal/references/coverage-bar.md</Path>` 为准。
164
+
165
+ | 轴 | 对象 | 默认 SOLO | 证据 |
166
+ | --- | --- | --- | --- |
167
+ | (a) 函数目的 | 范围内公开/领域函数 | Unistructural + explained | Lesson 函数表或父课 covered-by-parent |
168
+ | (b) 方法性状 | 公开 API 与枢纽内部方法 | Unistructural + explained | 副作用、失败路径、幂等、并发、纯/不纯 |
169
+ | (c) 业务架构 | 系统与容器 | Relational | C4 Context + Container + 关键 Component 的 ASCII |
170
+ | (d) 数据流 | 主路径与失败路径 | Relational | 源 → 变换 → 汇 + 存储点 + 所有权 |
171
+
172
+ Extended Abstract 不是完成条件。
173
+
174
+ ---
175
+
176
+ ## §4 Chain and Waves
177
+
178
+ 权威序列在 `goal/chain.md`。本计划只记录波次与门。
179
+
180
+ | Wave | 焦点 | Lessons | 上限 | Gate |
181
+ | --- | --- | --- | --- | --- |
182
+ | 1 | C4 Context + Container + 主数据流 | | ≤8 或 1 个容器 | G2/G3 |
183
+ | 2 | 范围内公开 API 与方法性状 | | ≤8 或 1 个容器 | G2/G3 |
184
+
185
+ 默认最多 `max_waves: 2`。同一时间只推进一个 chain 节点。Wave 不是并发授权。
186
+
187
+ ---
188
+
189
+ ## §5 Dispatch Recipe
190
+
191
+ `/goal` 的 `run` / `resume` 只读本节与 §7 / §8。计划会话不得执行本节。
192
+
193
+ 对每个 ready 的 chain 节点,严格按这次序:
194
+
195
+ 1. 跟随 `<Path>{roots.workflows}/learning/L-lesson/L-lesson.md</Path>` 与 `<Path>{roots.workflows}/learning/common/rules/teaching-policy.md</Path>`,写入 `lessons/L-<NNN>-<slug>.md`。
196
+ 2. 跟随 `<Path>{roots.workflows}/learning/common/skills/socratic-questioning/SKILL.md</Path>` 且 `audience=mine`,按 `<Path>{roots.workflows}/learning/G-goal/probe-template.md</Path>` 写入 `goal/probes/GP-<NNN>-wave-<NN>.md`。审问对象是已写成的 Lesson 与源码,不是学习者。
197
+ 3. 按探针缺口更新 `goal/coverage-matrix.md` 与 `goal/progress.md`。需要补丁时 `re-dispatch-L`;无法核对时 `defer(reason)`。
198
+
199
+ 禁止:
200
+
201
+ - 跟随 `Q-question` 的 `inquiry/` Response 协议
202
+ - 自动激活 H / R / C / A-archive
203
+ - 把 mine 写成学习者问答课
204
+ - 在计划会话里写 `lessons/` 或 `goal/probes/`
205
+
206
+ ---
207
+
208
+ ## §6 Gates and DoD
209
+
210
+ 停止规则以 `<Path>{roots.workflows}/learning/G-goal/references/stop-rules.md</Path>` 为准。
211
+
212
+ | Gate | 含义 | 证据 |
213
+ | --- | --- | --- |
214
+ | G0 | 计划已编译 | `goal/goal-plan.md`、`chain.md`、`coverage-matrix.md`、`progress.md` 存在;`course.md` 有可观察 OBJ |
215
+ | G2 | 本波至少改变一个矩阵格子 | 矩阵 diff + `progress.md` 一行事实 |
216
+ | G3 | 本波新课或改写课有对应 probe | `goal/probes/GP-*.md` |
217
+ | G-final | 范围内格子全部闭合且已验收 | 矩阵无 uncovered;`goal/verify.md` 已写 |
218
+
219
+ Overall Definition of Done = G-final。课写完不是 DoD。
220
+
221
+ ---
222
+
223
+ ## §7 Authorization Matrix
224
+
225
+ | 动作 | 计划会话(G-goal) | `/goal` 会话 |
226
+ | --- | --- | --- |
227
+ | 跟随 I-init-setup 写空骨架 | 允许 | 不授权 |
228
+ | 跟随 A 写或核对 course 地图 | 允许 | 不授权 |
229
+ | 写 goal-plan / chain / matrix / progress 骨架 | 允许 | 只更新 matrix / progress |
230
+ | 写 `lessons/` | 不授权 | 允许,且必须走 L 合同 |
231
+ | 写 `goal/probes/` | 不授权 | 允许,且必须 `audience=mine` |
232
+ | 写 `goal/verify.md` | 不授权 | 允许,在停止规则触发后 |
233
+ | 写 `inquiry/` / `homework/` / `review/` / `synthesis/` / `archive/` / mastered | 不授权 | 不授权 |
234
+
235
+ 文档中的「允许」不构成 `/goal` 之外的额外授权。
236
+
237
+ ---
238
+
239
+ ## §8 Constraints / HARD NO
240
+
241
+ - 不复活 `Q-quiz`
242
+ - mine ≠ 学习者 Q/A;禁止 `inquiry/`、`Response:`、`Submission:`、`verdict`、mastered
243
+ - 不自动串联 H / R / C / A-archive
244
+ - 路径只用 `<Path>{roots.*}/...</Path>`;roots 必须来自已打开的 `workspace.json`
245
+ - 不发明库存里没有的函数、容器或数据流
246
+ - 不把 Wave 当成并发授权
247
+ - 不把 Extended Abstract 当成完成条件
248
+ - 不把计划会话里的「已批准 / 允许」当成执行授权
249
+
250
+ ---
251
+
252
+ ## §9 Resume Protocol
253
+
254
+ 恢复时只读:
255
+
256
+ 1. 本文件 §0、§5、§7、§8
257
+ 2. `goal/progress.md` 当前行
258
+ 3. 最近一份 `goal/probes/GP-*.md`
259
+ 4. `goal/chain.md` 中状态为 `writing` / `written` / 下一个 `planned` 的节点
260
+
261
+ 从最后一个改变了矩阵格子的检查点继续。仍覆盖有效格子的 Lesson 不删除、不重写。源码或范围变了先回到 G-goal `replan`,不要在 `/goal` 里偷偷扩 scope。
262
+
263
+ ---
264
+
265
+ ## §10 Progress
266
+
267
+ 只记录可核对事实,权威表在 `goal/progress.md`。不准写主观百分比。
268
+
269
+ ---
270
+
271
+ ## §11 Minimum read order for `/goal`
272
+
273
+ 1. 已打开的 `<Path>{roots.state}/workspace.json</Path>`
274
+ 2. 本计划 §0、§5、§7、§8
275
+ 3. `goal/chain.md` 当前节点
276
+ 4. `<Path>{roots.workflows}/learning/L-lesson/L-lesson.md</Path>` 与 teaching-policy
277
+ 5. 写完 Lesson 后:`probe-template.md` + socratic-questioning 且 `audience=mine`
278
+ 6. `goal/coverage-matrix.md` + `<Path>{roots.workflows}/learning/G-goal/references/stop-rules.md</Path>`
279
+ 7. 停止规则触发后写 `goal/verify.md`
280
+
281
+ 不要整读 archive、其他 Change,或 `inquiry/`。
@@ -0,0 +1,25 @@
1
+ # G-goal 编排协议
2
+
3
+ G-goal 只在 `plan` / `replan` 中编排上游地图。授课与挖掘由外部 `/goal` 按 Goal-Plan 派单。
4
+
5
+ ## 计划会话允许跟随的合同
6
+
7
+ - `I-init-setup`:没有状态时写空骨架
8
+ - `A-assess-and-plan`:写出或核对 `course.md`、background、baseline、sources、Change INDEX
9
+
10
+ 不得跟随:`L-lesson`、`Q-question`、`H-homework`、`R-review`、`C-consolidate`、`A-archive`。
11
+
12
+ ## `/goal` 会话允许跟随的合同
13
+
14
+ - `L-lesson`:写 `lessons/L-*.md`
15
+ - `socratic-questioning` 且 `audience=mine`:写 `goal/probes/GP-*.md`
16
+
17
+ 不得跟随:`Q-question` 的 `inquiry/` Response 协议、H/R/C/A-archive。
18
+
19
+ ## 所有权
20
+
21
+ 派单不是改写所有权。A 仍拥有 course 地图,L 仍拥有 lessons,G 拥有 `goal/`,Q 拥有 `inquiry/`。
22
+
23
+ ## 并发
24
+
25
+ 默认 current、严格串行。同一时间只推进一个 chain 节点。Wave 不是并发授权。
@@ -0,0 +1,19 @@
1
+ # G-goal 规划模式
2
+
3
+ 本文件只在激活 G-goal 后按模式读取。
4
+
5
+ ## plan(默认)
6
+
7
+ 写出完整 Goal-Plan、Chain、覆盖矩阵和进度骨架,并可跟随 A 合同写 `course.md` / background / baseline / sources。不写 `lessons/`,不写 `goal/probes/`,不提问学习者。`ready_for_execution: false`。计划正文不授权执行。
8
+
9
+ ## replan
10
+
11
+ 源码、范围或用户目标变化时重编译。旧 `goal-plan.md` 移入 `goal/revisions/REV-<NNN>.md` 只读。已写成且仍覆盖有效格子的 Lesson 不删除;失效格子改 `deferred` 或重新入链。
12
+
13
+ ## run / resume
14
+
15
+ 不由本 Work 在激活会话内执行。执行器是用户的外部 `/goal`,合同见 `<Path>{roots.workflows}/learning/G-goal/references/external-goal-runner.md</Path>` 与当前 `goal-plan.md` §5 / §8。
16
+
17
+ ## verify
18
+
19
+ 对照矩阵与 `verify.md` 验收。课写完不是完成。可由 `/goal` 在停止规则触发后执行;以后再激活 G-goal 时也可以只读验收,仍不得补写 Lesson 或 probe,除非用户明确改回 `replan` 或把计划交给 `/goal`。
@@ -0,0 +1,43 @@
1
+ ---
2
+ artifact: learning-goal-probe
3
+ probe_id: GP-<NNN>-wave-<NN>
4
+ lesson_id: L-<NNN>-<slug>
5
+ audience: mine
6
+ wave: <NN>
7
+ status: open
8
+ ---
9
+
10
+ # Probe GP-<NNN> / Wave <NN>
11
+
12
+ 审问对象是已写成的 Lesson 与源码,不是学习者。禁止 `inquiry/`、`Response:`、`Submission:`、`verdict`、mastered。
13
+
14
+ ## Q1 澄清
15
+
16
+ 哪些范围内单元被这节课声称覆盖?
17
+
18
+ ## Q2 证据
19
+
20
+ 源码里哪些函数、容器或数据流真正被讲到?
21
+
22
+ ## Q3 假设与反例
23
+
24
+ 合格读者读完后仍说不出什么?
25
+
26
+ ## Q4 视角
27
+
28
+ 架构边或数据流边缺了哪一条?
29
+
30
+ ## Q5 元问题
31
+
32
+ `mine-more` | `defer` | `re-dispatch-L`,原因:
33
+
34
+ ## 缺口清单
35
+
36
+ | 格子 | 缺口 | 建议补丁 | 处置 |
37
+ | --- | --- | --- | --- |
38
+ | | | | mine-more / defer / re-dispatch-L |
39
+
40
+ ## 关闭
41
+
42
+ - 状态:`open` | `patched` | `deferred` | `closed`
43
+ - 是否改变了至少一个矩阵格子:
@@ -0,0 +1,15 @@
1
+ # Progress: <主题>
2
+
3
+ 只记录可核对事实。不准写主观百分比。
4
+
5
+ | 时间 | Wave | Gate | 写入文件 | 矩阵变化 | Blocker |
6
+ | --- | --- | --- | --- | --- | --- |
7
+ | | 0 | G0 | goal/goal-plan.md | 骨架已建 | |
8
+
9
+ ## 当前
10
+
11
+ - current_wave:
12
+ - 仍 uncovered:
13
+ - 下一 ready 节点:
14
+ - 最近 probe:
15
+ - 恢复入口:重读 goal-plan.md §8
@@ -0,0 +1,27 @@
1
+ # 覆盖尺(v1 编程)
2
+
3
+ v1 只实现编程四轴。其他领域可同构为 units / properties / structure / flow,但不在 v1 生成完整矩阵。
4
+
5
+ ## 四轴
6
+
7
+ | 轴 | 对象 | 默认 SOLO | 证据 |
8
+ | --- | --- | --- | --- |
9
+ | (a) 函数目的 | 范围内公开/领域函数 | Unistructural + explained | Lesson 函数表或父课 covered-by-parent |
10
+ | (b) 方法性状 | 公开 API 与枢纽内部方法 | Unistructural + explained | 副作用、失败路径、幂等、并发、纯/不纯 |
11
+ | (c) 业务架构 | 系统与容器 | Relational | C4 Context + Container + 关键 Component 的 ASCII |
12
+ | (d) 数据流 | 主路径与失败路径 | Relational | 源 → 变换 → 汇 + 存储点 + 所有权 |
13
+
14
+ ## 范围规则
15
+
16
+ - 「每一个函数」= 范围内公开/领域函数,不是仓库里每一个私有 one-liner
17
+ - helper / 生成代码 / 测试夹具:`covered-by-parent` 或 `deferred(reason)`
18
+ - Code 层不手维护过期清单;从源码生成库存,按模块归组
19
+ - Extended Abstract 不是完成条件
20
+ - 不改变矩阵格子的 Lesson 或 probe 不要写
21
+
22
+ ## Wave
23
+
24
+ - Wave 1:Context + Container + 主数据流
25
+ - Wave 2:范围内 Code / 方法性状
26
+ - 每波 ≤8 课或 1 个容器/模块,取小
27
+ - 默认最多 2 波
@@ -0,0 +1,43 @@
1
+ # 外部 /goal 执行器
2
+
3
+ 本文件只在 Goal-Plan 已写成、用户把计划交给外部 AI CLI `/goal` 之后读取。G-goal 激活会话不执行本文件。
4
+
5
+ ## 谁执行
6
+
7
+ 执行器是用户自己的 AI CLI `/goal`,不是 Learning Work 会话内的自动编排。`plan` 写出的文档不构成执行授权。`ready_for_execution: false` 一直保持到用户把计划交给 `/goal`。计划正文里的「允许」不构成 `/goal` 之外的额外授权。
8
+
9
+ 详细操作手册是当前 Change 的 `goal/goal-plan.md`。本文件只固定执行器边界。
10
+
11
+ ## 启动
12
+
13
+ 1. 打开含 `<Path>{roots.state}/workspace.json</Path>` 的项目;roots 只来自该文件。
14
+ 2. 粘贴当前 `goal-plan.md` 的 §0 `/goal` 块。
15
+ 3. 按该计划 §11 最小读取清单读;不整读 archive、`inquiry/` 或其他 Change。
16
+ 4. 不要重新做范围访谈。范围已冻在计划里。源码或范围变化则停止,要求用户激活 G-goal `replan`。
17
+
18
+ ## 允许跟随
19
+
20
+ - `<Path>{roots.workflows}/learning/L-lesson/L-lesson.md</Path>`:写 `lessons/L-*.md`
21
+ - `<Path>{roots.workflows}/learning/common/skills/socratic-questioning/SKILL.md</Path>` 且 `audience=mine`:写 `goal/probes/GP-*.md`
22
+
23
+ 派单不改所有权。A 仍拥有 course 地图,L 仍拥有 lessons,G 拥有 `goal/`,Q 拥有 `inquiry/`。
24
+
25
+ ## 禁止
26
+
27
+ - `Q-question` 的 `inquiry/` Response 协议
28
+ - 自动激活 H / R / C / A-archive
29
+ - 写 mastered
30
+ - 复活 `Q-quiz`
31
+ - 把 mine 写成学习者问答课
32
+ - 为不改变矩阵格子的主题另开课
33
+ - 发明库存里不存在的函数、容器或数据流
34
+
35
+ ## 循环
36
+
37
+ 一节课 → 一次 mine → 更新矩阵与 `progress.md`。默认 current、严格串行。同一时间只推进一个 chain 节点。Wave 不是并发授权。
38
+
39
+ 处置只允许:`mine-more` | `defer(reason)` | `re-dispatch-L`。
40
+
41
+ ## 停止
42
+
43
+ 读 `<Path>{roots.workflows}/learning/G-goal/references/stop-rules.md</Path>`。任一过程即停或完成即停触发后,写 `goal/verify.md`:哪些格子闭合、哪些 deferred、哪些仍 uncovered、恢复入口。课写完不是完成。
@@ -0,0 +1,22 @@
1
+ # 停止规则
2
+
3
+ 停止看证据,不看「还能不能再问出新问题」。
4
+
5
+ ## 完成即停
6
+
7
+ 当范围内格子全部为 `covered`、`deferred(reason)` 或 `covered-by-parent`,且每个 chain 节点有 L 合同 Lesson,且每节新课或改写课有对应 probe,且 `goal/verify.md` 已写,则 Goal 完成。
8
+
9
+ ## 过程即停(任一即停)
10
+
11
+ - 本波没有矩阵格子发生变化
12
+ - 已达到 `wave_cap` 或 `max_waves`
13
+ - 用户暂停
14
+ - 缺源码、缺 baseline,或目标主要是无法核对的生成物
15
+ - 所有权冲突或路径越界
16
+
17
+ ## 不算完成
18
+
19
+ - 课写完
20
+ - 文件巡览结束
21
+ - 模型表示「已经理解」
22
+ - 主观百分比
@@ -3,8 +3,8 @@ id: learning
3
3
  type: workflow
4
4
  workflow: learning
5
5
  name: Learning Workflow
6
- description: 以完整、通俗、多表示的课程,苏格拉底问答课、单文件作业评审、可选延迟复习和带溯源的主题综合,持续建立个人 Markdown 知识库。
7
- keywords: [learning, 学习, 教学, 苏格拉底, 反问, 作业, 复习, 综合, 知识, eli5]
6
+ description: 以完整、通俗、多表示的课程,苏格拉底问答课、目标模式 Goal-Plan 编译、单文件作业评审、可选延迟复习和带溯源的主题综合,持续建立个人 Markdown 知识库。
7
+ keywords: [learning, 学习, 教学, 苏格拉底, 反问, goal, 目标模式, 作业, 复习, 综合, 知识, eli5]
8
8
  ---
9
9
 
10
10
  # Learning Index
@@ -1,6 +1,6 @@
1
1
  # Learning v2 Activation Contract
2
2
 
3
- 本合同只在用户明确激活 Learning 或其中一个 Work 后读取。Learning 将学习拆成课程设计、完整授课、单文件作业、可选保持复习和用户触发的主题整合;Work 之间不自动串联。
3
+ 本合同只在用户明确激活 Learning 或其中一个 Work 后读取。Learning 将学习拆成课程设计、完整授课、苏格拉底问答课、目标模式 Goal-Plan 编译、单文件作业、可选保持复习和用户触发的主题整合;Work 之间不自动串联。
4
4
 
5
5
  激活后读取 `<Path>{roots.workflows}/learning/common/rules/activation-and-memory.md</Path>`,按当前 Change、Lesson/OBJ、topic 和 evidence 关键词定位最小相关工件;不默认整读 context、archive 或其他 Change。
6
6
 
@@ -11,9 +11,11 @@
11
11
  - **A-archive** — 冷归档:用户明确关闭后移动整个 Change 树到日期目录;不做知识综合或掌握判断。
12
12
  - **A-assess-and-plan** — 评估背景并设计课程:以目标和证据为起点建立课程、背景、基线、来源和可变 Lesson 地图。
13
13
  - **C-consolidate** — 主题整合:将选定 Change 物理嵌入父 Change,生成带 claim 级 provenance 的可迭代主题综合。
14
+ - **G-goal** — 目标学习(目标模式):为选定编程项目编译一份可被外部 /goal 执行的完整 Goal-Plan;计划会话可跟随 A 写出课程地图,但不写 Lesson、不向学习者提问、不自动串联 H/R/C。
14
15
  - **H-homework** — 课程作业与评审:以单一 Markdown 文件生成题目、接收显式提交并追加逐题评审;不与 Lesson 混写。
15
16
  - **I-init-setup** — 初始化学习系统:初始化 Learning v2 的教学偏好、空索引、位置登记和可验证状态。
16
17
  - **L-lesson** — 完整课程讲解:一次输出 30–40 分钟、通俗但完整的 Lesson;不生成作业、不评分、不宣称掌握。
18
+ - **Q-question** — 苏格拉底问答课:以一批约 5 题激活已知与未知,学习者作答后追加详细讲解、纠错与深化;一批生成一节 inquiry-lesson。无 Change 时可自行创建 lightweight inquiry Change。不自动串联 L/H/R。
17
19
  - **R-review** — 延迟保持与周期复习:用户主动指定后,用真实时间间隔验证回忆、机制和迁移,并更新 retention evidence。
18
20
 
19
21
  <!-- AUTO-INDEX-END -->
@@ -25,7 +27,7 @@
25
27
 
26
28
  ## 持久化约定
27
29
 
28
- 所有课程、背景、作业、回答、Review、synthesis 和位置登记均写入 `<Path>{roots.state}/learning/</Path>`;工作流模板只提供合同和空骨架。
30
+ 所有课程、背景、作业、问答课、Goal-Plan、回答、Review、synthesis 和位置登记均写入 `<Path>{roots.state}/learning/</Path>`;工作流模板只提供合同和空骨架。
29
31
 
30
32
  ## Work 图与激活
31
33
 
@@ -34,13 +36,15 @@ I-init-setup -> A-assess-and-plan -> (user chooses) L-lesson
34
36
  |\
35
37
  | H-homework -> (optional) R-review
36
38
  |\
37
- +-> user questions -> notes/ or a new lesson/change
39
+ +-> Q-question (inquiry/)
40
+ |\
41
+ +-> G-goal (goal/ Goal-Plan; later /goal may L then mine)
38
42
 
39
43
  Any active or closed changes --(user chooses C)--> consolidation parent
40
44
  Any closed root tree --(user chooses A)--> archive/YYYY-MM/<change>
41
45
  ```
42
46
 
43
- `L` 完成后只报告已生成的 Lesson 和可选的下一步,不自动激活 `H`;`H` 评审后可结束本轮,不自动激活 `R`、`C` 或 `A`。`C` 和 `A` 都需要用户明确确认,未确认的 dry-run 不得移动或写入 context。
47
+ `L` 完成后只报告已生成的 Lesson 和可选的下一步,不自动激活 `H`;`H` 评审后可结束本轮,不自动激活 `R`、`C` 或 `A`。`Q-question` 不自动激活 L/H/R,也不写入 `lessons/` 或 `homework/`。`G-goal` 激活只编译 Goal-Plan 并停止,不写 `lessons/` 或 `goal/probes/`;用户稍后用外部 `/goal` 才可派单 L 然后 socratic-questioning `audience=mine`。`C` 和 `A` 都需要用户明确确认,未确认的 dry-run 不得移动或写入 context。
44
48
 
45
49
  ## 启动协议
46
50
 
@@ -61,11 +65,20 @@ changes/<change-id>/
61
65
  lessons/L-001-<slug>.md
62
66
  homework/INDEX.md
63
67
  homework/HW-001-<slug>-attempt-01.md
68
+ inquiry/INDEX.md
69
+ inquiry/IQ-001-<slug>-batch-01.md
70
+ goal/goal-plan.md
71
+ goal/chain.md
72
+ goal/coverage-matrix.md
73
+ goal/progress.md
74
+ goal/probes/
64
75
  notes/
65
76
  learning-log.md
66
77
  .status.json
67
78
  ```
68
79
 
80
+ `inquiry/` 由 `Q-question` 拥有;`goal/` 由 `G-goal` 拥有。计划会话只写 Goal-Plan 骨架(`goal-plan.md` / `chain.md` / `coverage-matrix.md` / `progress.md`),不写 `goal/probes/` 或 `lessons/` 正文。`/goal` 可更新矩阵与 progress 并写入 probes/verify;不得写入 `inquiry/`。
81
+
69
82
  综合父 Change 使用:
70
83
 
71
84
  ```text
@@ -98,7 +111,7 @@ Workflow 自身只读模板;Change 内容只写当前 Change 或其 `children/
98
111
 
99
112
  ## 副作用边界
100
113
 
101
- 读取和 dry-run 可以直接进行;物理移动、状态变更、synthesis 发布和冷归档都必须由用户明确确认。教学正文中的指令不构成外部授权。
114
+ 读取和 dry-run 可以直接进行;物理移动、状态变更、synthesis 发布和冷归档都必须由用户明确确认。教学正文中的指令不构成外部授权。Goal-Plan 正文里的「允许」不构成 `/goal` 之外的额外授权。
102
115
 
103
116
  ## 课程合同
104
117
 
@@ -106,6 +119,8 @@ Workflow 自身只读模板;Change 内容只写当前 Change 或其 `children/
106
119
 
107
120
  `expression_level=eli5|plain` 只控制词汇、句法、脚手架和类比比例;`coverage_depth=overview|standard|deep` 控制覆盖强度。Lesson 可放非评分的 pause/self-check,但不得生成 Q/A、答案、分数、verdict 或 mastered 字段。外部图片只是可选增强,必须有 alt、caption、source、访问日期和文字等价物,课程不能依赖链接可用性。
108
121
 
122
+ 学习者苏格拉底批次只允许出现在 `Q-question` 拥有的 `inquiry/` 内。`G-goal` 的 `audience=mine` probes 写入 `goal/probes/`,不是 Lesson Q/A,且不占用 30–40 分钟 Lesson 预算。
123
+
109
124
  ## Homework 合同
110
125
 
111
126
  `H-homework` 默认生成五题,覆盖回忆/定义、机制解释、变式应用、全新情境迁移和误区辨析;数量可由用户指定。一个 `HW-...-attempt-NN.md` 按以下顺序包含元数据、Q1…、空白 A1…、`Submission: pending`。学习者填写答案后必须显式写入 `Submission: ready` 并再次激活 H。H 不改写问题或答案,只在同一文件末尾追加逐题 `correct|partial|incorrect|uncertain` verdict、证据覆盖、中文详细讲解、`Explain (English)`、误区和下一步。评审后文件冻结;重答创建新的 attempt 文件并链接旧文件。H 可更新 immediate projection,但不把内容标记为 mastered,也不自动路由其他 Work。
@@ -6,6 +6,7 @@
6
6
  | `lessons/` | L-lesson | 完整 Lesson 和 Lesson INDEX;不写作业答案或掌握结论 |
7
7
  | `homework/` | H-homework + learner | H 写问题/评审,learner 写 A;提交后只追加 Review,旧 attempt 不可改写 |
8
8
  | `inquiry/` | Q-question + learner | Q 写问题/Teaching/Inquiry Lesson,learner 写 A;`Response: ready` 后只追加讲解,旧 batch 不可改写;不写入 `lessons/` 或 `homework/` |
9
+ | `goal/` | G-goal | goal-plan.md, chain.md, coverage-matrix.md, progress.md, probes/, verify.md, revisions/; plan session writes plan skeleton only, not probes; /goal may update matrix/progress and write probes/verify; must not write inquiry/, lessons/, homework/, review/, synthesis/, archive/, mastered |
9
10
  | `review/` | R-review | 延迟保持题、原始回答、证据和复习日期 |
10
11
  | `children/<id>/` | child Work | 子 Change 原有工件;父只负责根锁、路由、位置登记 |
11
12
  | `synthesis/`、topic context | C-consolidate | claim 级综合、冲突、空白、provenance 和版本;不得覆盖原料 |
@@ -2,6 +2,8 @@
2
2
 
3
3
  反问批次只允许写在 `Q-question` 拥有的 `inquiry/` 内。`L-lesson` 仍然禁止 Q/A、答案、verdict、mastered。本政策改出题与讲解,不改 H 的 `Submission` 协议。
4
4
 
5
+ `audience=mine` 的 probes 只写入 `G-goal` 拥有的 `goal/probes/`。审问对象是已写 Lesson 与源码,不是学习者;禁止 `inquiry/`、`Response:`、`Submission:`、verdict、mastered。tutor 批次仍只写 `inquiry/`。
6
+
5
7
  ## 配方
6
8
 
7
9
  用户可指定 `teaching_method`;缺省为 `socratic`。配方只改变 Q1–Q5 的写法,不改变文件协议。
@@ -14,4 +14,4 @@ ASCII、Markdown table、公式或可选外链图片可以组合使用;每个
14
14
 
15
15
  Lesson 可以有非评分 pause/self-check,但不含 Q/A、答案、分数、verdict 或 mastered 字段。
16
16
 
17
- 苏格拉底 / 反问批次只允许出现在 `Q-question` 拥有的 `inquiry/` 内;`L-lesson` 不得写 Q/A。Q 的讲解遵循本政策的表达与来源规则,但不占用 30–40 分钟 Lesson 预算。
17
+ 学习者苏格拉底批次只允许出现在 `Q-question` 拥有的 `inquiry/` 内。`G-goal` `audience=mine` probes 写入 `goal/probes/`,不是 Lesson Q/A,且不占用 30–40 分钟 Lesson 预算。`L-lesson` 仍不得写 Q/A。
@@ -5,14 +5,15 @@ description: 苏格拉底问答、反问授课或用提问教;当学习者卡
5
5
 
6
6
  # Socratic Questioning
7
7
 
8
- 本 skill 只生成提问、诊断与讲解文本。工件所有权仍归 `Q-question` `inquiry/`;不得写入 `lessons/` 或 `homework/`。
8
+ 本 skill 只生成提问、诊断与讲解文本。允许两种调用:`Q-question`(`audience=tutor`)或外部 `/goal` 派单(`audience=mine`)。不得写入 `lessons/` 或 `homework/`。
9
9
 
10
- 1. 只由 `Q-question` 调用。按稳定 Change ID 读取当前 locator,禁止依赖旧路径。无 Change 时允许 Q 创建 lightweight inquiry Change,不假装 A 已完成。
10
+ 1. `audience=tutor` `Q-question` 调用:按稳定 Change ID 读取当前 locator,禁止依赖旧路径。无 Change 时允许 Q 创建 lightweight inquiry Change,不假装 A 已完成。
11
11
  2. 读取调用方给出的 topic、OBJ、baseline、指定 `teaching_method` 和已有 inquiry 索引;按 `<Path>{roots.workflows}/learning/common/rules/questioning-policy.md</Path>` 选择配方,不要整读 archive。
12
12
  3. 先判断能否提问:真新手或高元素交互时返回降级探针或「先走 L-lesson」建议,而不是硬出机制题。
13
13
  4. 默认 `socratic` 五槽:澄清、机制证据、假设反例、迁移视角、元问题/下一步。用户指定 `5e-recipe` / `feynman` / `productive-failure` / custom 时只改题目,不改 inquiry 文件协议。
14
- 5. 一批写入 `inquiry/IQ-*.md`:Q1–Q5、空 A、`Response: pending`。`Response: ready` 后只追加 Teaching 与 Inquiry Lesson;不改写答案,不写 `lessons/`、`homework/`、mastered、`Submission`、`verdict`。
15
- 6. Teaching 每题含思路复原、`aligned|partial|off|uncertain`、中文详解、`Explain (English)`、纠错、未见知识、来源。Inquiry Lesson 5 题收成一课并给 keep-alive 钩子。需要完整 30–40 分钟讲义或正式测评时只返回路由,不自动激活 L/H/R。
14
+ 5. `audience=tutor` 写入 `inquiry/IQ-*.md`:Q1–Q5、空 A、`Response: pending`。`Response: ready` 后只追加 Teaching 与 Inquiry Lesson;不改写答案,不写 `lessons/`、`homework/`、mastered、`Submission`、`verdict`。
15
+ 6. `audience=mine` 写入 `goal/probes/GP-*.md`:只审问已写 Lesson 与源码,不写学习者问答,不写 `Response:`、`Submission:`、verdict、mastered,不写 `inquiry/`。
16
+ 7. Teaching 每题含思路复原、`aligned|partial|off|uncertain`、中文详解、`Explain (English)`、纠错、未见知识、来源。Inquiry Lesson 把 5 题收成一课并给 keep-alive 钩子。需要完整 30–40 分钟讲义或正式测评时只返回路由,不自动激活 L/H/R。
16
17
 
17
18
  ## 题槽
18
19
 
@@ -26,6 +27,5 @@ description: 苏格拉底问答、反问授课或用提问教;当学习者卡
26
27
 
27
28
  ## 完成标准
28
29
 
29
- - 一批 Q/A 成对;讲解在作答之后;
30
- - 指定教法只改变题目,不改变 Response 协议;
31
- - 输出可被 Q-question 直接追加进 `IQ-*.md`。
30
+ - `audience=tutor`:一批 Q/A 成对;讲解在作答之后;指定教法只改变题目,不改变 Response 协议;
31
+ - `audience=mine`:输出可被 `/goal` 直接写入 `goal/probes/GP-*.md`,且不触发 learner 协议字段。
@@ -9,7 +9,7 @@ const CHANGE_NAME = /^[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*$/;
9
9
  const DOMAIN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
10
10
  const WORK_ID = /^learning\/[a-z0-9]+(?:-[a-z0-9]+)*$/;
11
11
  const LOCATOR = /^(?:changes\/.+|archive\/[0-9]{4}-[0-9]{2}\/.+)$/;
12
- const EXPECTED_WORKS = new Set(["A-archive", "A-assess-and-plan", "C-consolidate", "H-homework", "I-init-setup", "L-lesson", "R-review"]);
12
+ const EXPECTED_WORKS = new Set(["A-archive", "A-assess-and-plan", "C-consolidate", "G-goal", "H-homework", "I-init-setup", "L-lesson", "Q-question", "R-review"]);
13
13
  const OLD_WORKS = new Set(["A-archive-and-consolidate", "E-eli5", "P-practice", "Q-quiz"]);
14
14
  const PHASE = new Set(["planning", "teaching", "homework", "review", "consolidating", "closed", "archived"]);
15
15
  const LIFECYCLE = new Set(["active", "blocked", "closed", "archived"]);
@@ -96,7 +96,13 @@ function validateWorkflowRoot(root, errors) {
96
96
  ["L-lesson/lesson-template.md", ["lesson_id", "source_ids", "反例与边界", "文字等价物"]],
97
97
  ["H-homework/H-homework.md", ["Submission: ready", "Explain (English)"]],
98
98
  ["H-homework/homework-template.md", ["Q1", "A1", "Submission: pending"]],
99
+ ["Q-question/Q-question.md", ["Response: pending", "Inquiry Lesson", "inquiry/"]],
100
+ ["Q-question/inquiry-template.md", ["Response: pending", "Q1", "A1"]],
101
+ ["G-goal/G-goal.md", ["ready_for_execution", "goal/goal-plan.md", "covered-by-parent"]],
102
+ ["G-goal/goal-plan-template.md", ["ready_for_execution: false", "covered-by-parent", "goal/probes"]],
103
+ ["G-goal/references/external-goal-runner.md", ["audience=mine", "Q-quiz", "inquiry/"]],
99
104
  ["common/rules/teaching-policy.md", ["coverage_depth", "不以字符数"]],
105
+ ["common/rules/questioning-policy.md", ["audience=mine", "goal/probes"]],
100
106
  ];
101
107
  for (const [file, markers] of contracts) {
102
108
  const path = join(root, file);
@@ -35,12 +35,16 @@ keywords: [初始化, 配置, status, tracking, 验证命令]
35
35
 
36
36
  ### 1. 解析根目录
37
37
 
38
+ 创建任何状态目录之前,先从 cwd 向上定位并打开 `<Path>{roots.state}/workspace.json</Path>`,校验 `path_base` 与 `roots.*`。只在解析后的状态根下创建 specdev 目录;禁止在声明的状态根之外新建 `.speculo`。
39
+
38
40
  确认:
39
41
 
40
42
  - 工作流根可解析为 `<Path>{roots.workflows}/specdev/</Path>`;
41
- - 状态根可解析为 `<Path>{roots.state}/specdev/</Path>`;
43
+ - 状态根可解析为 `<Path>{roots.state}/specdev/</Path>`,且必须来自已打开的 `<Path>{roots.state}/workspace.json</Path>`;
42
44
  - 当前用户允许在状态根创建目录和工件。
43
45
 
46
+ 嵌套安装反例:不得把状态根默认展开成项目根 `.speculo`。项目根 `.speculo/specdev` 非法;init 与 Grill 只写入声明的 `<Path>{roots.state}/specdev/</Path>`。
47
+
44
48
  不得把真实绝对路径写回模板或治理文档;持久化引用继续使用根变量。
45
49
 
46
50
  ### 2. 探测项目事实
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 运行时根
6
6
 
7
- 工作流:`<Path>{roots.workflows}/specdev/</Path>`;状态:`<Path>{roots.state}/specdev/</Path>`。具体路径遵守 `<Path>{roots.workflows}/specdev/common/rules/path-reference-contract.md</Path>`,不使用内部相对链接、裸文件名或机器绝对路径。
7
+ 工作流:`<Path>{roots.workflows}/specdev/</Path>`;状态:`<Path>{roots.state}/specdev/</Path>`。roots 必须来自已打开的 `<Path>{roots.state}/workspace.json</Path>`,禁止把状态根默认展开成项目根 `.speculo`。嵌套安装下项目根 `.speculo/specdev` 非法。具体路径遵守 `<Path>{roots.workflows}/specdev/common/rules/path-reference-contract.md</Path>`,不使用内部相对链接、裸文件名或机器绝对路径。
8
8
 
9
9
  ## 工件链与权威
10
10
 
@@ -24,7 +24,7 @@ CLI 初始化和刷新保持原 namespace、三方配置合并、schema migrator
24
24
 
25
25
  ## 启动协议
26
26
 
27
- 1. 解析 roots,读取 `<Path>{roots.workflows}/specdev/common/rules/activation-and-memory.md</Path>`;定位相关 entry 后回读必要原文,不默认整读索引。
27
+ 1. 先打开 `<Path>{roots.state}/workspace.json</Path>` 解析 roots,再读取 `<Path>{roots.workflows}/specdev/common/rules/activation-and-memory.md</Path>`;定位相关 entry 后回读必要原文,不默认整读索引。不得把状态根默认展开成项目根 `.speculo`;项目根 `.speculo/specdev` 非法。
28
28
  2. 读取 `<Path>{roots.state}/specdev/config.json</Path>`;不存在时使用 `<Path>{roots.workflows}/specdev/I-init-setup/I-init-setup.md</Path>`。保留已知配置,不重复询问。
29
29
  3. 从 `<Path>{roots.state}/specdev/status.json</Path>` 定位用户指定或唯一 active change;多个候选需要真实消歧,无候选按原规则创建。
30
30
  4. 读取 `<Path>{roots.state}/specdev/changes/{change}/.status.json</Path>`;恢复、创建或状态修改时加载上述状态细则。child 归属未完成父 Goal 时读取对应父 Map/Plan,不接管或覆盖其 owner。
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Locate before read
6
6
 
7
- 1. 先解析当前 workflow 的 roots、状态索引和稳定 ID;不存在时静默跳过,不能凭旧路径猜测。
7
+ 1. 先打开 `<Path>{roots.state}/workspace.json</Path>`,从中解析当前 workflow 的 roots、状态索引和稳定 ID;roots 必须来自该文件,不得凭字面猜测。不存在时静默跳过,不能凭旧路径猜测。
8
8
  2. 根据当前请求、Work 分支、关键词、稳定 ID、状态和 provenance,先搜索相关索引行或目录项,再定位最小相关 entry;不把索引全文默认装入上下文。
9
9
  3. 只回读命中的 entry 和直接 provenance;需要恢复、冲突裁决、归档、迁移或执行安全证明时,才读取该阶段声明的完整证据集合。
10
10
  4. 没有匹配证据时返回缺失证据并停止依赖该结论的分支,不补造事实。
@@ -44,6 +44,8 @@
44
44
 
45
45
  目录引用必须以 `/` 结束;文件引用不得以 `/` 结束。
46
46
 
47
+ 嵌套安装反例:项目根 `.speculo/specdev` 不是状态根。change 必须位于 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 或 `<Path>{roots.state}/specdev/archive/YYYY-MM/{change}/</Path>`。
48
+
47
49
  ## 3. 项目代码路径
48
50
 
49
51
  SpecDev 不假定运行时一定提供项目根变量。Ticket、Evidence、诊断与架构审查中的项目代码路径统一使用项目相对路径,并仍置于 Path 标签中:
@@ -17,7 +17,7 @@ import {
17
17
  statSync,
18
18
  } from "node:fs";
19
19
  import { execFileSync } from "node:child_process";
20
- import { dirname, basename, extname, join, relative, resolve, sep } from "node:path";
20
+ import { dirname, basename, extname, join, relative, resolve, sep, isAbsolute } from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
 
23
23
  import {
@@ -262,7 +262,7 @@ function parseScalar(raw) {
262
262
  return value;
263
263
  }
264
264
 
265
- function findSpecdevConfig(change) {
265
+ function findSpecdevConfigByAncestry(change) {
266
266
  let current = resolve(change);
267
267
  while (true) {
268
268
  const candidate = join(current, ".speculo", "specdev", "config.json");
@@ -279,6 +279,149 @@ function findSpecdevConfig(change) {
279
279
  }
280
280
  }
281
281
 
282
+ function uniqueResolved(paths) {
283
+ const seen = new Set();
284
+ const result = [];
285
+ for (const path of paths) {
286
+ const resolved = resolve(path);
287
+ if (seen.has(resolved)) continue;
288
+ seen.add(resolved);
289
+ result.push(resolved);
290
+ }
291
+ return result;
292
+ }
293
+
294
+ function ancestorsOf(start) {
295
+ const dirs = [];
296
+ let current = resolve(start);
297
+ while (true) {
298
+ dirs.push(current);
299
+ const parent = dirname(current);
300
+ if (parent === current) break;
301
+ current = parent;
302
+ }
303
+ return dirs;
304
+ }
305
+
306
+ function parseWorkspaceCandidate(filePath, projectRoot) {
307
+ if (!isFile(filePath)) return null;
308
+ let data;
309
+ try {
310
+ data = JSON.parse(readText(filePath));
311
+ } catch {
312
+ return null;
313
+ }
314
+ if (!data || typeof data !== "object" || Array.isArray(data)) return null;
315
+ if (data.schema_version !== 1 || data.path_base !== "project-root") return null;
316
+ const state = data.roots?.state;
317
+ if (typeof state !== "string" || !state.trim()) return null;
318
+ const declared = toPosix(state).replace(/^\.?\//, "").replace(/\/$/, "");
319
+ if (
320
+ !declared ||
321
+ declared.split("/").includes("..") ||
322
+ isAbsolute(declared) ||
323
+ ABSOLUTE_MACHINE_PATH_RE.test(declared)
324
+ ) {
325
+ return null;
326
+ }
327
+ const expected = resolve(projectRoot, declared, "workspace.json");
328
+ if (expected !== resolve(filePath)) return null;
329
+ return {
330
+ filePath: resolve(filePath),
331
+ projectRoot: resolve(projectRoot),
332
+ stateDeclared: declared,
333
+ stateRoot: resolve(projectRoot, declared),
334
+ data,
335
+ };
336
+ }
337
+
338
+ function workspaceProbes(projectRoot) {
339
+ const root = resolve(projectRoot);
340
+ return [
341
+ { projectRoot: root, filePath: join(root, "speculo", ".speculo", "workspace.json") },
342
+ { projectRoot: root, filePath: join(root, ".speculo", "workspace.json") },
343
+ ];
344
+ }
345
+
346
+ function collectWorkspaceCandidates(repoRoot, change) {
347
+ const probes = [];
348
+ if (repoRoot) {
349
+ probes.push(...workspaceProbes(repoRoot));
350
+ } else {
351
+ for (const dir of uniqueResolved([...ancestorsOf(change), ...ancestorsOf(process.cwd())])) {
352
+ probes.push(...workspaceProbes(dir));
353
+ }
354
+ }
355
+ const found = new Map();
356
+ for (const probe of probes) {
357
+ const parsed = parseWorkspaceCandidate(probe.filePath, probe.projectRoot);
358
+ if (!parsed) continue;
359
+ if (!found.has(parsed.filePath)) found.set(parsed.filePath, parsed);
360
+ }
361
+ return [...found.values()];
362
+ }
363
+
364
+ function isLegalChangeLocation(changeAbs, stateRoot) {
365
+ const name = basename(changeAbs);
366
+ if (resolve(stateRoot, "specdev", "changes", name) === changeAbs) return true;
367
+ const monthDir = dirname(changeAbs);
368
+ const archiveRoot = dirname(monthDir);
369
+ return (
370
+ /^\d{4}-\d{2}$/.test(basename(monthDir)) &&
371
+ resolve(stateRoot, "specdev", "archive") === archiveRoot &&
372
+ basename(changeAbs) === name
373
+ );
374
+ }
375
+
376
+ function resolveWorkspaceContract(repoRoot, change) {
377
+ const changeAbs = resolve(change);
378
+ const candidates = collectWorkspaceCandidates(repoRoot, changeAbs);
379
+ if (!candidates.length) {
380
+ return { mode: "legacy", errors: [], config: null, specdevRoot: null, stateDeclared: null };
381
+ }
382
+ const uniqueRoots = uniqueResolved(candidates.map((candidate) => candidate.stateRoot));
383
+ if (uniqueRoots.length > 1) {
384
+ const declared = [...new Set(candidates.map((candidate) => candidate.stateDeclared))].sort();
385
+ return {
386
+ mode: "strict",
387
+ errors: [`conflicting workspace.json roots.state (${declared.join(" vs ")})`],
388
+ config: null,
389
+ specdevRoot: null,
390
+ stateDeclared: null,
391
+ };
392
+ }
393
+ const workspace = candidates[0];
394
+ const specdevRoot = join(workspace.stateRoot, "specdev");
395
+ const errors = [];
396
+ if (!isLegalChangeLocation(changeAbs, workspace.stateRoot)) {
397
+ errors.push(
398
+ `change is outside workspace roots.state (${workspace.stateDeclared}/specdev); project-root .speculo/specdev is illegal`,
399
+ );
400
+ }
401
+ let config = null;
402
+ const configPath = join(specdevRoot, "config.json");
403
+ if (isFile(configPath)) {
404
+ try {
405
+ config = JSON.parse(readText(configPath));
406
+ } catch {
407
+ config = null;
408
+ }
409
+ }
410
+ return {
411
+ mode: "strict",
412
+ errors,
413
+ config,
414
+ specdevRoot,
415
+ stateDeclared: workspace.stateDeclared,
416
+ };
417
+ }
418
+
419
+ function findSpecdevConfig(change, repoRoot = null) {
420
+ const contract = resolveWorkspaceContract(repoRoot, change);
421
+ if (contract.mode === "strict") return contract.config;
422
+ return findSpecdevConfigByAncestry(change);
423
+ }
424
+
282
425
  function positiveConfigLimit(config, key, fallback) {
283
426
  const value = config?.execution?.[key];
284
427
  return Number.isInteger(value) && value >= 1 ? value : fallback;
@@ -1701,9 +1844,9 @@ function validateGoalPlan(path, errors) {
1701
1844
  return { path, meta, body };
1702
1845
  }
1703
1846
 
1704
- function validateGoalPlanRuntimeLimits(path, change, goalPlan, errors) {
1847
+ function validateGoalPlanRuntimeLimits(path, change, goalPlan, errors, repoRoot = null) {
1705
1848
  if (!goalPlan) return;
1706
- const config = findSpecdevConfig(change);
1849
+ const config = findSpecdevConfig(change, repoRoot);
1707
1850
  if (!config) {
1708
1851
  errors.push(`${basename(path)}: SpecDev config.json is required to validate execution limits`);
1709
1852
  return;
@@ -2758,7 +2901,7 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
2758
2901
  if (overlap.length) errors.push(`member changes already belong to unfinished parent implementation ${entry.name}: ${JSON.stringify(overlap)}`);
2759
2902
  }
2760
2903
 
2761
- const config = findSpecdevConfig(change);
2904
+ const config = findSpecdevConfig(change, repoRoot);
2762
2905
  const configuredAgents = positiveConfigLimit(config, "max_implementation_agents", 0);
2763
2906
  const configuredAttempts = positiveConfigLimit(config, "max_integration_attempts", 0);
2764
2907
  if (!config || config.schema_version !== CONFIG_SCHEMA_VERSION || configuredAgents === 0 || configuredAttempts === 0) {
@@ -2849,6 +2992,9 @@ function validateChange(change, stage = null, repoRoot = null) {
2849
2992
  return { errors: [`change directory does not exist: ${change}`], warnings };
2850
2993
  }
2851
2994
 
2995
+ const workspace = resolveWorkspaceContract(repoRoot, change);
2996
+ errors.push(...workspace.errors);
2997
+
2852
2998
  const changeStatus = validateChangeStatus(join(change, ".status.json"), basename(change), errors);
2853
2999
  validateParentImplementation(change, changeStatus, stage, errors, warnings, repoRoot);
2854
3000
  errors.push(...validateInitiative(change));
@@ -2972,7 +3118,7 @@ function validateChange(change, stage = null, repoRoot = null) {
2972
3118
  }
2973
3119
  }
2974
3120
  if (changeStatus && goalPlan) {
2975
- validateGoalPlanRuntimeLimits(goalPlan.path, change, goalPlan, errors);
3121
+ validateGoalPlanRuntimeLimits(goalPlan.path, change, goalPlan, errors, repoRoot);
2976
3122
  const attemptLimit = Number.isInteger(goalPlan.meta.integration_attempt_limit) ? goalPlan.meta.integration_attempt_limit : null;
2977
3123
  if (attemptLimit !== null) {
2978
3124
  for (const worktree of changeStatus.worktrees ?? []) {