@namewta/speculo 1.0.8 → 1.0.10
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/template/.speculo/README.md +1 -1
- package/template/canonical/canonical-specdev-goal-plan.md +1 -1
- package/template/canonical/canonical-specdev-grill-with-docs.md +1 -1
- package/template/canonical/canonical-specdev-spec.md +1 -1
- package/template/canonical/canonical-specdev-tickets.md +1 -1
- package/template/canonical/canonical-specdev-wayfinder.md +1 -1
- package/template/workflows/learning/INDEX.md +2 -2
- package/template/workflows/learning/Q-question/Q-question.md +45 -0
- package/template/workflows/learning/Q-question/inquiry-template.md +69 -0
- package/template/workflows/learning/Q-question/lightweight-course-template.md +31 -0
- package/template/workflows/learning/common/rules/artifact-contract.md +1 -0
- package/template/workflows/learning/common/rules/questioning-policy.md +38 -0
- package/template/workflows/learning/common/rules/teaching-policy.md +2 -0
- package/template/workflows/learning/common/skills/socratic-questioning/SKILL.md +31 -0
- package/template/workflows/specdev/I-init-setup/I-init-setup.md +5 -1
- package/template/workflows/specdev/README.md +2 -2
- package/template/workflows/specdev/common/rules/activation-and-memory.md +1 -1
- package/template/workflows/specdev/common/rules/path-reference-contract.md +2 -0
- 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** |
|
|
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 |
|
|
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
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
653
|
+
1. 先打开 `workspace.json`,从中解析当前 workflow 的 roots、状态索引和稳定 ID;roots 必须来自该文件,不得凭字面猜测。不存在时静默跳过,不能凭旧路径猜测。
|
|
654
654
|
2. 根据当前请求、Work 分支、关键词、稳定 ID、状态和 provenance,先搜索相关索引行或目录项,再定位最小相关 entry;不把索引全文默认装入上下文。
|
|
655
655
|
3. 只回读命中的 entry 和直接 provenance;需要恢复、冲突裁决、归档、迁移或执行安全证明时,才读取该阶段声明的完整证据集合。
|
|
656
656
|
4. 没有匹配证据时返回缺失证据并停止依赖该结论的分支,不补造事实。
|
|
@@ -3,8 +3,8 @@ id: learning
|
|
|
3
3
|
type: workflow
|
|
4
4
|
workflow: learning
|
|
5
5
|
name: Learning Workflow
|
|
6
|
-
description:
|
|
7
|
-
keywords: [learning, 学习, 教学, 作业, 复习, 综合, 知识, eli5]
|
|
6
|
+
description: 以完整、通俗、多表示的课程,苏格拉底问答课、单文件作业评审、可选延迟复习和带溯源的主题综合,持续建立个人 Markdown 知识库。
|
|
7
|
+
keywords: [learning, 学习, 教学, 苏格拉底, 反问, 作业, 复习, 综合, 知识, eli5]
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Learning Index
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: learning/question
|
|
3
|
+
type: workflow-entry
|
|
4
|
+
workflow: learning
|
|
5
|
+
name: 苏格拉底问答课
|
|
6
|
+
description: 以一批约 5 题激活已知与未知,学习者作答后追加详细讲解、纠错与深化;一批生成一节 inquiry-lesson。无 Change 时可自行创建 lightweight inquiry Change。不自动串联 L/H/R。
|
|
7
|
+
keywords: [反问, 苏格拉底, socratic, inquiry, 提问课, questioning, 激活, 问答课]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 苏格拉底问答课
|
|
11
|
+
|
|
12
|
+
> 激活本 Work 后,先读取 `<Path>{roots.workflows}/learning/README.md</Path>`。
|
|
13
|
+
|
|
14
|
+
## 读取范围
|
|
15
|
+
|
|
16
|
+
1. 先读取 `<Path>{roots.workflows}/learning/README.md</Path>` 与当前 Work 的状态入口。
|
|
17
|
+
2. 再读取 `<Path>{roots.workflows}/learning/common/rules/activation-and-memory.md</Path>`,按当前分支、状态和关键词定位最小相关工件。
|
|
18
|
+
3. 只在本 Work 明确要求恢复、冲突、执行安全、创建 Change 或归档证据时扩展为全量读取;缺少匹配证据或 owner/gateway 时停止受影响分支。
|
|
19
|
+
|
|
20
|
+
## 流程
|
|
21
|
+
|
|
22
|
+
1. 解析 roots 与 Learning v2 状态。若尚无 `status.json` / `locations.json`,先按 I-init-setup 写入空状态骨架,不创建知识条目。
|
|
23
|
+
2. 若没有可用 Change:根据用户主题创建 lightweight inquiry Change `YYYY-MM-DD-<kebab-topic>`,写入 `INDEX.md`、最小 `course.md`/`baseline.md`/`sources.md`、`inquiry/INDEX.md`、`learning-log.md` 和 `.status.json`(`phase=teaching`,`current_work=learning/question`)。不假装已完成 A-assess-and-plan 的完整课程地图。
|
|
24
|
+
3. 若已有 Change:按 Activation 合同只读取对应 course/OBJ/Lesson/baseline/notes/inquiry 索引;不整读 archive 或其他 Change。
|
|
25
|
+
4. 读取 `<Path>{roots.workflows}/learning/common/skills/socratic-questioning/SKILL.md</Path>` 与 `<Path>{roots.workflows}/learning/common/rules/questioning-policy.md</Path>`。按用户指定教法或默认 `socratic` 配方生成一批约 5 题。
|
|
26
|
+
5. 创建 `inquiry/IQ-<NNN>-<slug>-batch-NN.md`:元数据、Q1…Q5、空白 A1…A5、`Response: pending`。更新 `inquiry/INDEX.md`。不得把答案写入题目。
|
|
27
|
+
6. 学习者填写 A1…并写入精确行 `Response: ready` 后再次激活本 Work。校验回答原文与标记,冻结 Q/A。
|
|
28
|
+
7. 只在同一文件末尾追加 `## Teaching` 与 `## Inquiry Lesson`。Teaching 逐题给出思路复原、`aligned|partial|off|uncertain`、中文详解、`Explain (English)`、纠错路径、先前未覆盖知识和来源锚点。Inquiry Lesson 把本批 5 题收成一节短课单元,并以 keep-alive 钩子指向下一批、缺失 OBJ、L、H 或 R。将 `Response:` 更新为 `closed`。
|
|
29
|
+
8. 更新 `learning-log.md` 与 Change `.status.json`:`works_run` 追加 `learning/question`,清空 `current_work`。不写 mastered,不写入 `lessons/` 或 `homework/`,不自动激活其他 Work。重答必须新建 batch 文件并链接旧文件。
|
|
30
|
+
|
|
31
|
+
## 完成标准
|
|
32
|
+
|
|
33
|
+
- 一批默认 5 题;数量可由用户指定,但必须 Q/A 成对且连续编号;
|
|
34
|
+
- `Response: pending|ready|closed` 状态可审计;ready 之前不得出现 Teaching / Inquiry Lesson;
|
|
35
|
+
- Teaching 是授课讲解,不是 H 的评分 Review,不得使用 `Submission` 或 `verdict: correct|partial|incorrect` 字段名;
|
|
36
|
+
- 5 题生成一节 Inquiry Lesson;完整 30–40 分钟讲义仍归 L-lesson;
|
|
37
|
+
- 无 Change 时本 Work 可自行创建 lightweight inquiry Change,不得因缺少 A 产物而拒绝开问;
|
|
38
|
+
- 真新手或高元素交互时必须提供降级探针或建议先走 L-lesson。
|
|
39
|
+
|
|
40
|
+
## 子文件
|
|
41
|
+
|
|
42
|
+
- 问答课模板:`<Path>{roots.workflows}/learning/Q-question/inquiry-template.md</Path>`
|
|
43
|
+
- 轻量课程模板:`<Path>{roots.workflows}/learning/Q-question/lightweight-course-template.md</Path>`
|
|
44
|
+
- 提问政策:`<Path>{roots.workflows}/learning/common/rules/questioning-policy.md</Path>`
|
|
45
|
+
- 提问引擎:`<Path>{roots.workflows}/learning/common/skills/socratic-questioning/SKILL.md</Path>`
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
inquiry_id: IQ-<NNN>
|
|
3
|
+
batch: 01
|
|
4
|
+
lesson_unit_id: IQ-<NNN>
|
|
5
|
+
objective_ids: [OBJ-01]
|
|
6
|
+
question_count: 5
|
|
7
|
+
teaching_method: socratic
|
|
8
|
+
expression_level: plain
|
|
9
|
+
coverage_depth: standard
|
|
10
|
+
source_ids: [S-001]
|
|
11
|
+
created_at: <ISO-8601>
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# 苏格拉底问答课:<主题>
|
|
15
|
+
|
|
16
|
+
## 引用
|
|
17
|
+
|
|
18
|
+
- Change:`<stable change id and current locator>`
|
|
19
|
+
- 目标:`<OBJ ids>`
|
|
20
|
+
- 教法:`socratic | 5e-recipe | feynman | productive-failure | custom`
|
|
21
|
+
- Skill:`socratic-questioning`
|
|
22
|
+
|
|
23
|
+
## Questions
|
|
24
|
+
|
|
25
|
+
### Q1 — 澄清与定义
|
|
26
|
+
|
|
27
|
+
<问题>
|
|
28
|
+
|
|
29
|
+
### Q2 — 机制与证据
|
|
30
|
+
|
|
31
|
+
<问题>
|
|
32
|
+
|
|
33
|
+
### Q3 — 假设与反例
|
|
34
|
+
|
|
35
|
+
<问题>
|
|
36
|
+
|
|
37
|
+
### Q4 — 迁移与视角
|
|
38
|
+
|
|
39
|
+
<问题>
|
|
40
|
+
|
|
41
|
+
### Q5 — 元问题与下一步
|
|
42
|
+
|
|
43
|
+
<问题>
|
|
44
|
+
|
|
45
|
+
## Learner Answers
|
|
46
|
+
|
|
47
|
+
### A1
|
|
48
|
+
|
|
49
|
+
<在此填写,不要改写 Q1>
|
|
50
|
+
|
|
51
|
+
### A2
|
|
52
|
+
|
|
53
|
+
<在此填写,不要改写 Q2>
|
|
54
|
+
|
|
55
|
+
### A3
|
|
56
|
+
|
|
57
|
+
<在此填写,不要改写 Q3>
|
|
58
|
+
|
|
59
|
+
### A4
|
|
60
|
+
|
|
61
|
+
<在此填写,不要改写 Q4>
|
|
62
|
+
|
|
63
|
+
### A5
|
|
64
|
+
|
|
65
|
+
<在此填写,不要改写 Q5>
|
|
66
|
+
|
|
67
|
+
Response: pending
|
|
68
|
+
|
|
69
|
+
<!-- Q 在 Response: ready 后只能追加 Teaching 与 Inquiry Lesson;不得改写以上内容。不得使用 Submission 或 verdict。 -->
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 课程设计:<主题>(inquiry-first)
|
|
2
|
+
|
|
3
|
+
本 Change 由 `Q-question` 在没有现成课程时创建。它只冻结主题、可观察目标和开问范围,不假装 `A-assess-and-plan` 已完成完整 Lesson 地图。
|
|
4
|
+
|
|
5
|
+
## 目标与期望效果
|
|
6
|
+
|
|
7
|
+
| ID | 可观察目标 | 关键性 | 前置 OBJ | 证据类型 | Inquiry | Lesson | Homework |
|
|
8
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
9
|
+
| OBJ-01 | `<能用自己的话解释什么>` | 是 | `<none>` | `<解释/边界/下一步>` | IQ-001 | 可选 | 可选 |
|
|
10
|
+
|
|
11
|
+
## 学习者与表达/深度配置
|
|
12
|
+
|
|
13
|
+
| 字段 | 值 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| expression_level | `plain` |
|
|
16
|
+
| coverage_depth | `standard` |
|
|
17
|
+
| 来源 | inquiry-first;完整背景由后续 A 补全 |
|
|
18
|
+
|
|
19
|
+
## 课程地图
|
|
20
|
+
|
|
21
|
+
先用苏格拉底问答课激活已知与未知;需要 30–40 分钟完整讲义时再激活 `L-lesson`,需要正式测评时再激活 `H-homework`。
|
|
22
|
+
|
|
23
|
+
## 成功证据与范围外
|
|
24
|
+
|
|
25
|
+
- 成功:至少一批 `Response: closed` 的 Inquiry Lesson,并留下 keep-alive 钩子。
|
|
26
|
+
- 范围外:不在本 Change 写入 `lessons/` 或 `homework/`,不宣称 mastered。
|
|
27
|
+
|
|
28
|
+
## Revision 记录
|
|
29
|
+
|
|
30
|
+
| 时间 | 变化 | 原因 | 是否升级为完整 A 课程 |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
| `course.md`、`background/`、`baseline.md`、`sources.md`、Change `INDEX.md` | A-assess-and-plan | 目标、宏观基础、原始基线、来源和课程地图;实质修改留 revision |
|
|
6
6
|
| `lessons/` | L-lesson | 完整 Lesson 和 Lesson INDEX;不写作业答案或掌握结论 |
|
|
7
7
|
| `homework/` | H-homework + learner | H 写问题/评审,learner 写 A;提交后只追加 Review,旧 attempt 不可改写 |
|
|
8
|
+
| `inquiry/` | Q-question + learner | Q 写问题/Teaching/Inquiry Lesson,learner 写 A;`Response: ready` 后只追加讲解,旧 batch 不可改写;不写入 `lessons/` 或 `homework/` |
|
|
8
9
|
| `review/` | R-review | 延迟保持题、原始回答、证据和复习日期 |
|
|
9
10
|
| `children/<id>/` | child Work | 子 Change 原有工件;父只负责根锁、路由、位置登记 |
|
|
10
11
|
| `synthesis/`、topic context | C-consolidate | claim 级综合、冲突、空白、provenance 和版本;不得覆盖原料 |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 苏格拉底问答政策
|
|
2
|
+
|
|
3
|
+
反问批次只允许写在 `Q-question` 拥有的 `inquiry/` 内。`L-lesson` 仍然禁止 Q/A、答案、verdict、mastered。本政策改出题与讲解,不改 H 的 `Submission` 协议。
|
|
4
|
+
|
|
5
|
+
## 配方
|
|
6
|
+
|
|
7
|
+
用户可指定 `teaching_method`;缺省为 `socratic`。配方只改变 Q1–Q5 的写法,不改变文件协议。
|
|
8
|
+
|
|
9
|
+
| teaching_method | Q1 | Q2 | Q3 | Q4 | Q5 |
|
|
10
|
+
| --- | --- | --- | --- | --- | --- |
|
|
11
|
+
| `socratic` | 澄清/定义 | 机制/证据 | 假设与反例 | 迁移/他者视角 | 元问题/下一步 |
|
|
12
|
+
| `5e-recipe` | Engage | Explore | Explain-prompt | Elaborate | Evaluate-as-question |
|
|
13
|
+
| `feynman` | 用自己的话定义 | 举生活例子 | 指出类比失效 | 教给外行 | 还缺哪一块 |
|
|
14
|
+
| `productive-failure` | 未教过的难题 | 你怎么试 | 卡在哪 | 规范解会怎么走 | 和你的差在哪 |
|
|
15
|
+
| 用户自定 | 编译进同一 5 槽,缺槽用 `socratic` 补 |
|
|
16
|
+
|
|
17
|
+
每题标注 `objective_id`、`bloom_level`、`socratic_move`、`expected_evidence`、`difficulty`。数量默认 5,可由用户改,但必须 Q/A 成对连续。
|
|
18
|
+
|
|
19
|
+
## 文件协议
|
|
20
|
+
|
|
21
|
+
- 出题文件含空 A 与精确行 `Response: pending`。
|
|
22
|
+
- 学习者作答后写 `Response: ready`,再次激活 Q。
|
|
23
|
+
- Teaching / Inquiry Lesson 只能追加在 ready 之后;不得改写 Q/A。
|
|
24
|
+
- 闭环后 `Response: closed`。重答新建 `IQ-…-batch-NN`,旧文件只读。
|
|
25
|
+
- 禁止 `Submission:`、禁止 H 的 `verdict: correct|partial|incorrect` 字段名。教学判定用 `aligned|partial|off|uncertain`。
|
|
26
|
+
- 禁止写入 `lessons/` 或 `homework/`。
|
|
27
|
+
|
|
28
|
+
## 讲解与 keep-alive
|
|
29
|
+
|
|
30
|
+
`## Teaching` 每题必须有:思路复原、判定、中文详解、`Explain (English)`、纠错路径、先前未覆盖知识、来源锚点。
|
|
31
|
+
`## Inquiry Lesson` 把本批收成一节短课:地图、机制、边界、稳定误区、下一步激活钩子。
|
|
32
|
+
每一份 Teaching 结尾必须有 keep-alive 钩子(下一批种子、缺失 OBJ、建议 L/H/R),不得把本批写成终点。
|
|
33
|
+
|
|
34
|
+
## 新手逃逸与 Change
|
|
35
|
+
|
|
36
|
+
baseline 显示真新手或主题元素交互很高时,先降难度探针,或建议激活 `L-lesson` 再建模型。不强制 question-first。卡住连续三题仍无进展时给脚手架,而不是直接灌完整答案进 Q 文本。
|
|
37
|
+
|
|
38
|
+
无可用 Change 且用户已激活本 Work 时,允许创建 lightweight inquiry Change;不得因缺少 A 产物而拒绝开问,也不得复活已退役的 `Q-quiz`。
|
|
@@ -13,3 +13,5 @@
|
|
|
13
13
|
ASCII、Markdown table、公式或可选外链图片可以组合使用;每个视觉必须有 caption、alt 和附近的完整文字等价物,外链失效不能阻塞理解。`sources.md` 和 Lesson source table 记录 URL、标题、定位、访问日期、claim 映射、可信度和不确定性;无法验证的内容明确标为 open/uncertain。
|
|
14
14
|
|
|
15
15
|
Lesson 可以有非评分 pause/self-check,但不含 Q/A、答案、分数、verdict 或 mastered 字段。
|
|
16
|
+
|
|
17
|
+
苏格拉底 / 反问批次只允许出现在 `Q-question` 拥有的 `inquiry/` 内;`L-lesson` 不得写 Q/A。Q 的讲解遵循本政策的表达与来源规则,但不占用 30–40 分钟 Lesson 预算。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: socratic-questioning
|
|
3
|
+
description: 苏格拉底问答、反问授课或用提问教;当学习者卡住、不知道下一步、指定提问/5E/费曼/生产性失败,或 Q-question 需要出题与讲解配方时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Socratic Questioning
|
|
7
|
+
|
|
8
|
+
本 skill 只生成提问、诊断与讲解文本。工件所有权仍归 `Q-question` 的 `inquiry/`;不得写入 `lessons/` 或 `homework/`。
|
|
9
|
+
|
|
10
|
+
1. 只由 `Q-question` 调用。按稳定 Change ID 读取当前 locator,禁止依赖旧路径。无 Change 时允许 Q 创建 lightweight inquiry Change,不假装 A 已完成。
|
|
11
|
+
2. 读取调用方给出的 topic、OBJ、baseline、指定 `teaching_method` 和已有 inquiry 索引;按 `<Path>{roots.workflows}/learning/common/rules/questioning-policy.md</Path>` 选择配方,不要整读 archive。
|
|
12
|
+
3. 先判断能否提问:真新手或高元素交互时返回降级探针或「先走 L-lesson」建议,而不是硬出机制题。
|
|
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。
|
|
16
|
+
|
|
17
|
+
## 题槽
|
|
18
|
+
|
|
19
|
+
| 槽 | 动作 | Paul 类 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Q1 | 澄清/定义 | clarification |
|
|
22
|
+
| Q2 | 机制/证据 | evidence |
|
|
23
|
+
| Q3 | 假设/反例 | assumptions |
|
|
24
|
+
| Q4 | 迁移/视角 | viewpoints |
|
|
25
|
+
| Q5 | 元问题/下一步 | question-the-question |
|
|
26
|
+
|
|
27
|
+
## 完成标准
|
|
28
|
+
|
|
29
|
+
- 一批 Q/A 成对;讲解在作答之后;
|
|
30
|
+
- 指定教法只改变题目,不改变 Response 协议;
|
|
31
|
+
- 输出可被 Q-question 直接追加进 `IQ-*.md`。
|
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
|
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 ?? []) {
|