project-tiny-context-harness 0.2.78 → 0.2.79
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 +4 -2
- package/assets/README.md +4 -2
- package/assets/README.zh-CN.md +3 -1
- package/assets/skills/superpowers-long-task/SKILL.md +25 -15
- package/dist/lib/superpowers-task-compile.js +4 -120
- package/dist/lib/superpowers-task-source-compile.d.ts +5 -0
- package/dist/lib/superpowers-task-source-compile.js +148 -0
- package/dist/lib/superpowers-task-source-parser.d.ts +23 -0
- package/dist/lib/superpowers-task-source-parser.js +218 -0
- package/dist/lib/superpowers-task-state-schema.d.ts +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,9 @@ The ordinary long-task path uses `/normal-long-task`. It is the non-Superpowers
|
|
|
96
96
|
|
|
97
97
|
The Superpowers long-task path uses `/superpowers-long-task` when three inputs already exist: `Product / Architecture Source`, `Technical Realization Plan` and `Acceptance Checklist`. The product/architecture source preserves original intent and scope; the technical realization plan is the execution blueprint and plan-conformance source; the checklist is the acceptance authority. The Skill does not perform complexity routing: invocation means Superpowers long-task execution was already selected. Two-document compatibility is allowed only when the first document clearly contains both product/architecture source and technical realization plan sections. If only a product/architecture source and checklist exist, the Skill stops with a Missing Fields Report for a missing `Technical Realization Plan` instead of generating one. The technical realization plan must already satisfy the required Superpowers-ready Markdown implementation plan fields; if it does, the prompt binds it directly to Superpowers execution rather than regenerating the plan, and if it does not, the Skill stops before generating a prompt. The prompt is Tiny Context's adapter layer, aligned to the official Superpowers skills while remaining a Tiny Context-owned adapter rather than an upstream-owned schema. It may wrap Superpowers with Tiny Context authority, conformance and acceptance gates, but it must not redefine or fork Superpowers execution mechanics. It requires parent-level `Product Context Delta` and `Technical Context Delta` checks before implementation and uses a canonical state kernel under `tmp/ty-context/plan-acceptance/<plan-slug>/`: `task-state.json` is the only execution state source, `events.ndjson` is append-only and `derived/**` contains generated local audit, plan-conformance matrix, final acceptance verdict, progress ledger, evidence index, context alignment and final summary views. Complete acceptance rows are externally reviewable evidence claims derived from `task-state.evidence[]`: the checklist supplies the proof chain, fresh reviewable evidence must satisfy every required layer, and material drift, missing layers or unapproved sibling substitution prevent `complete`. Goal-mode wording separates `audit_task_complete`, `acceptance_target_status` and computed `product_goal_complete`: implementation / execution goals complete only when `ty-context superpowers final-gate` computes `product_goal_complete=true`; read-only audit goals may end at `audit_task_complete`, but a non-accepted verdict says `Audit workflow completed; acceptance target not complete.` and does not use unqualified `Goal achieved` or `update_goal(status="complete")` as acceptance of the user target.
|
|
98
98
|
|
|
99
|
-
The three inputs also carry capability-first delivery boundaries. Product / Architecture Source declares `delivery_scope`, `full_population_required`, samples that validate the claim, samples that do not validate it and out-of-scope backlog. Each Technical Realization Plan item declares delivery scope, capability target, representative samples, full-population boundary and non-required population. Each Acceptance Checklist item declares acceptance scope, what it validates and does not validate, sample boundary and full-population requirement. `scope_conflict_requires_decision` blocks completion when source, plan and checklist disagree between system capability build, representative sample validation and full-population operation. Sample evidence or framework-only implementation cannot prove all-provider, all-interface, all-platform or full-population completion unless the AC explicitly allows it; when full population is not explicitly required, generated views report it as `not_in_scope`.
|
|
99
|
+
The three inputs also carry capability-first delivery boundaries. Product / Architecture Source declares `delivery_scope`, `full_population_required`, samples that validate the claim, samples that do not validate it and out-of-scope backlog. Each Technical Realization Plan item declares delivery scope, capability target, representative samples, full-population boundary and non-required population. Each Acceptance Checklist item declares acceptance scope, what it validates and does not validate, sample boundary and full-population requirement. `scope_conflict_requires_decision` blocks completion when source, plan and checklist disagree between system capability build, representative sample validation and full-population operation. Sample evidence or framework-only implementation cannot prove all-provider, all-interface, all-platform or full-population completion unless the AC explicitly allows it; when full population is not explicitly required, generated views report it as `not_in_scope`.
|
|
100
|
+
|
|
101
|
+
`ty-context superpowers compile` uses a strict heading-based grammar for that packet. Product / Architecture Source is one document-level object with fixed fields. Technical Realization Plan items are definitions only when written as Markdown headings such as `## PI-001: ...`; Acceptance Checklist items are definitions only when written as headings such as `## AC-001: ...`. Fields inside those sections must use fixed `key: value`, indented-list or `key: |` syntax. Plain prose, tables, mapping previews and ordinary lists that mention `PI-001` or `AC-001` are references, not definitions; old list-style definitions such as `- PI-001: ...` followed by delivery fields now fail at compile time with file and line guidance.
|
|
100
102
|
|
|
101
103
|
For non-trivial Superpowers slices, the generated prompt requires a structured `slice-delta.json`. The executor applies it with `ty-context superpowers apply-slice-delta <workdir> <slice-delta.json>`, then runs `ty-context superpowers derive` and `ty-context superpowers slice-gate`. Each delta records touched plan items/ACs, code changes, closed and remaining proof layers, blockers, cleanup assertions, `progress_value` and canonical evidence records with `proves`, `does_not_prove`, freshness, redaction and reviewability. Default slice guidance is to group 2-4 strongly related missing layers that share an AC, runtime scenario, proof environment or verification path, while single-gap slices are reserved for blockers, contradictions or small metadata cleanup. The prompt also asks executors to classify missing layers, reuse DB/API/Browser environments only with unique proof prefixes and cleanup assertions, and run a stale/overclaim scan after deriving artifacts.
|
|
102
104
|
|
|
@@ -159,7 +161,7 @@ npm ci
|
|
|
159
161
|
npm run smoke:quickstart
|
|
160
162
|
npm run preview:pack
|
|
161
163
|
cd /path/to/your/test-repo
|
|
162
|
-
npm install -D /path/to/project-tiny-context-harness/tmp/ty-context/source-preview/package/project-tiny-context-harness-0.2.
|
|
164
|
+
npm install -D /path/to/project-tiny-context-harness/tmp/ty-context/source-preview/package/project-tiny-context-harness-0.2.79.tgz
|
|
163
165
|
npx --no-install ty-context init --adopt
|
|
164
166
|
make validate-context
|
|
165
167
|
```
|
package/assets/README.md
CHANGED
|
@@ -94,7 +94,7 @@ That smoke packs the local workspace, installs it into a disposable repo, runs `
|
|
|
94
94
|
```sh
|
|
95
95
|
npm run preview:pack
|
|
96
96
|
cd /path/to/your/test-repo
|
|
97
|
-
npm install -D /path/to/project-tiny-context-harness/tmp/ty-context/source-preview/package/project-tiny-context-harness-0.2.
|
|
97
|
+
npm install -D /path/to/project-tiny-context-harness/tmp/ty-context/source-preview/package/project-tiny-context-harness-0.2.79.tgz
|
|
98
98
|
npx --no-install ty-context init --adopt
|
|
99
99
|
make validate-context
|
|
100
100
|
```
|
|
@@ -140,7 +140,9 @@ The ordinary long-task path uses `/normal-long-task`. It is the non-Superpowers
|
|
|
140
140
|
|
|
141
141
|
The Superpowers long-task path uses `/superpowers-long-task` when three inputs already exist: `Product / Architecture Source`, `Technical Realization Plan` and `Acceptance Checklist`. The product/architecture source preserves original intent and scope; the technical realization plan is the execution blueprint and plan-conformance source; the checklist is the acceptance authority. The Skill does not perform complexity routing: invocation means Superpowers long-task execution was already selected. Two-document compatibility is allowed only when the first document clearly contains both product/architecture source and technical realization plan sections. If only a product/architecture source and checklist exist, the Skill stops with a Missing Fields Report for a missing `Technical Realization Plan` instead of generating one. The technical realization plan must already satisfy the required Superpowers-ready Markdown implementation plan fields; if it does, the prompt binds it directly to Superpowers execution rather than regenerating the plan, and if it does not, the Skill stops before generating a prompt. The prompt is Tiny Context's adapter layer, aligned to the official Superpowers skills while remaining a Tiny Context-owned adapter rather than an upstream-owned schema. It may wrap Superpowers with Tiny Context authority, conformance and acceptance gates, but it must not redefine or fork Superpowers execution mechanics. It requires parent-level `Product Context Delta` and `Technical Context Delta` checks before implementation and uses a canonical state kernel under `tmp/ty-context/plan-acceptance/<plan-slug>/`: `task-state.json` is the only execution state source, `events.ndjson` is append-only and `derived/**` contains generated local audit, plan-conformance matrix, final acceptance verdict, progress ledger, evidence index, context alignment and final summary views. Complete acceptance rows are externally reviewable evidence claims derived from `task-state.evidence[]`: the checklist supplies the proof chain, fresh reviewable evidence must satisfy every required layer, and material drift, missing layers or unapproved sibling substitution prevent `complete`. Goal-mode wording separates `audit_task_complete`, `acceptance_target_status` and computed `product_goal_complete`: implementation / execution goals complete only when `ty-context superpowers final-gate` computes `product_goal_complete=true`; read-only audit goals may end at `audit_task_complete`, but a non-accepted verdict says `Audit workflow completed; acceptance target not complete.` and does not use unqualified `Goal achieved` or `update_goal(status="complete")` as acceptance of the user target.
|
|
142
142
|
|
|
143
|
-
The three inputs also carry capability-first delivery boundaries. Product / Architecture Source declares `delivery_scope`, `full_population_required`, samples that validate the claim, samples that do not validate it and out-of-scope backlog. Each Technical Realization Plan item declares delivery scope, capability target, representative samples, full-population boundary and non-required population. Each Acceptance Checklist item declares acceptance scope, what it validates and does not validate, sample boundary and full-population requirement. `scope_conflict_requires_decision` blocks completion when source, plan and checklist disagree between system capability build, representative sample validation and full-population operation. Sample evidence or framework-only implementation cannot prove all-provider, all-interface, all-platform or full-population completion unless the AC explicitly allows it; when full population is not explicitly required, generated views report it as `not_in_scope`.
|
|
143
|
+
The three inputs also carry capability-first delivery boundaries. Product / Architecture Source declares `delivery_scope`, `full_population_required`, samples that validate the claim, samples that do not validate it and out-of-scope backlog. Each Technical Realization Plan item declares delivery scope, capability target, representative samples, full-population boundary and non-required population. Each Acceptance Checklist item declares acceptance scope, what it validates and does not validate, sample boundary and full-population requirement. `scope_conflict_requires_decision` blocks completion when source, plan and checklist disagree between system capability build, representative sample validation and full-population operation. Sample evidence or framework-only implementation cannot prove all-provider, all-interface, all-platform or full-population completion unless the AC explicitly allows it; when full population is not explicitly required, generated views report it as `not_in_scope`.
|
|
144
|
+
|
|
145
|
+
`ty-context superpowers compile` uses a strict heading-based grammar for that packet. Product / Architecture Source is one document-level object with fixed fields. Technical Realization Plan items are definitions only when written as Markdown headings such as `## PI-001: ...`; Acceptance Checklist items are definitions only when written as headings such as `## AC-001: ...`. Fields inside those sections must use fixed `key: value`, indented-list or `key: |` syntax. Plain prose, tables, mapping previews and ordinary lists that mention `PI-001` or `AC-001` are references, not definitions; old list-style definitions such as `- PI-001: ...` followed by delivery fields now fail at compile time with file and line guidance.
|
|
144
146
|
|
|
145
147
|
For non-trivial Superpowers slices, the generated prompt requires a structured `slice-delta.json`. The executor applies it with `ty-context superpowers apply-slice-delta <workdir> <slice-delta.json>`, then runs `ty-context superpowers derive` and `ty-context superpowers slice-gate`. Each delta records touched plan items/ACs, code changes, closed and remaining proof layers, blockers, cleanup assertions, `progress_value` and canonical evidence records with `proves`, `does_not_prove`, freshness, redaction and reviewability. Default slice guidance is to group 2-4 strongly related missing layers that share an AC, runtime scenario, proof environment or verification path, while single-gap slices are reserved for blockers, contradictions or small metadata cleanup. The prompt also asks executors to classify missing layers, reuse DB/API/Browser environments only with unique proof prefixes and cleanup assertions, and run a stale/overclaim scan after deriving artifacts.
|
|
146
148
|
|
package/assets/README.zh-CN.md
CHANGED
|
@@ -56,7 +56,9 @@ Tiny Context 有两个核心层。Minimal Context 是长期事实源层:说明
|
|
|
56
56
|
|
|
57
57
|
Superpowers 长程任务 Skill 用 `/superpowers-long-task`。如果下一步明确要 Superpowers 目标模式文本,推荐在三份输入都存在后调用:`Product / Architecture Source`(产品/架构原始意图源)、`Technical Realization Plan`(具体技术实现方案)和 `Acceptance Checklist`(验收清单)。它不做复杂度分流;调用它表示上游已经决定使用 Superpowers 长程执行。它不要求先跑 `/normal-long-task`,但也不会把产品方案现场翻译成技术方案;如果只有产品/架构方案和验收清单,Skill 会用 Missing Fields Report 停止并报告缺少 `Technical Realization Plan`。两份输入兼容只限第一份明确包含产品/架构源和技术实现方案两个章节。`Technical Realization Plan` 必须已经满足 Superpowers-ready Markdown implementation plan 的必填字段;满足时它跳过方案生成,直接绑定 Superpowers 执行,不满足时直接中断并报告缺失字段,不生成 prompt。它显式输出 `Superpowers 输入包` 和执行绑定,让未来 executor 清楚哪些输入进入 parent-level Product Context Delta / Technical Context Delta、slice-level new durable fact check、subagent/inline execution、TDD、`superpowers:verification-before-completion`、canonical `task-state.json`、append-only `events.ndjson`、generated `derived/**` views、proof-chain evidence 和 optional auditor review。这个 prompt 是面向 Superpowers workflow 的 Tiny Context 适配层,对齐官方 Superpowers skills,但不是上游维护的 schema;它可以在 Superpowers 外层增加 Tiny Context 的权威、对图纸和验收门禁,但不能重新定义、重复或分叉 Superpowers 执行机制。如果未来改动让 Tiny Context 新增步骤和官方 Superpowers 职责冲突、重复或覆盖,应停止修改并提示边界冲突,不要静默合并两套流程。它不生成技术方案或验收清单、不执行计划、不证明完成,也不会把临时 state、derived views 或 verdict 注册成 `project_context/**`。三输入是上游权威,state / derived views / validator / auditor 不能改写它们。`task-state.json` 是唯一执行状态源,`events.ndjson` 追加记录状态变更,`derived/**` 只生成 local audit、plan-conformance matrix、final acceptance verdict、progress ledger、evidence index、context alignment 和 final summary 等阅读视图。完整验收行按外部审计证据处理:proof chain 来自验收清单,fresh evidence 必须通过 `task-state.evidence[]` 满足每个 required layer,存在 material drift、缺 required layer 或未批准 sibling substitution 时不能标 `complete`。Goal mode 表述必须区分 `audit_task_complete`、`acceptance_target_status` 和 computed `product_goal_complete`:实现/执行目标只在 `ty-context superpowers final-gate` 计算出 `product_goal_complete=true` 时完成;只读审计目标可在 `audit_task_complete` 时结束,但 verdict 不是 accepted/complete 时,回复写 `Audit workflow completed; acceptance target not complete.`,不能用未限定的 `Goal achieved` 或 `update_goal(status="complete")` 表示用户验收目标已完成。
|
|
58
58
|
|
|
59
|
-
三份输入还必须承载 capability-first delivery 边界。Product / Architecture Source 声明 `delivery_scope`、`full_population_required`、哪些 representative samples 能验证 claim、哪些不能验证、以及 `out_of_scope_backlog`。每个 Technical Realization Plan item 声明 delivery scope、capability target、representative samples、full-population boundary 和 non-required population。每个 Acceptance Checklist item 声明 acceptance scope、`ac_validates`、`ac_does_not_validate`、sample boundary 和 full-population requirement。source / plan / checklist 在 system capability build、representative sample validation、full population operation 之间冲突时,`scope_conflict_requires_decision` 阻塞完成。sample evidence 或 framework-only implementation 不能证明 all-provider、all-interface、all-platform 或 full-population 完成,除非 AC 明确批准;未显式要求 full population 时,generated views 必须报告 `not_in_scope`。
|
|
59
|
+
三份输入还必须承载 capability-first delivery 边界。Product / Architecture Source 声明 `delivery_scope`、`full_population_required`、哪些 representative samples 能验证 claim、哪些不能验证、以及 `out_of_scope_backlog`。每个 Technical Realization Plan item 声明 delivery scope、capability target、representative samples、full-population boundary 和 non-required population。每个 Acceptance Checklist item 声明 acceptance scope、`ac_validates`、`ac_does_not_validate`、sample boundary 和 full-population requirement。source / plan / checklist 在 system capability build、representative sample validation、full population operation 之间冲突时,`scope_conflict_requires_decision` 阻塞完成。sample evidence 或 framework-only implementation 不能证明 all-provider、all-interface、all-platform 或 full-population 完成,除非 AC 明确批准;未显式要求 full population 时,generated views 必须报告 `not_in_scope`。
|
|
60
|
+
|
|
61
|
+
`ty-context superpowers compile` 对这三份输入使用严格的 heading-based grammar。Product / Architecture Source 是一个文档级对象,只读固定字段。Technical Realization Plan 里的 PI 只有写成 `## PI-001: ...` 这类 Markdown heading 才是正式定义;Acceptance Checklist 里的 AC 也只有 `## AC-001: ...` 这类 heading 才是正式定义。heading section 内字段必须使用固定 `key: value`、缩进列表或 `key: |` 多行块。正文、表格、mapping preview、`related_acs` / `related_plan_items` 和普通列表里出现的 `PI-001` / `AC-001` 都只是引用;旧式 `- PI-001: ...` 或 `- AC-001: ...` 后面跟字段的写法会在 compile 阶段带文件和行号报错。
|
|
60
62
|
|
|
61
63
|
对于非平凡 slice,生成的 Superpowers prompt 要求使用结构化 `slice-delta.json`。executor 通过 `ty-context superpowers apply-slice-delta <workdir> <slice-delta.json>` 应用 delta,然后运行 `ty-context superpowers derive` 和 `ty-context superpowers slice-gate`。每个 delta 记录 touched plan items / ACs、code changes、closed / remaining proof layers、blockers、cleanup assertions、`progress_value`,以及带有 `proves`、`does_not_prove`、freshness、redaction 和 reviewability 的 canonical evidence records。默认 slice 策略是把同一 AC、runtime 场景、proof 环境或验证路径下的 2-4 个强相关 missing layers 合并处理;单 gap slice 只留给 blocker、contradiction 或小型 metadata cleanup。prompt 还会要求先分类 missing layer、复用 DB/API/Browser 环境时使用唯一 proof prefix 和 cleanup assertion,并在生成 derived artifacts 后做 stale/overclaim scan。
|
|
62
64
|
|
|
@@ -65,15 +65,24 @@ The input must fully expose these fields:
|
|
|
65
65
|
- explicitly non-completing shortcuts, such as local-only enhancement, old page continuing to own a moved responsibility, sampled path, plan-only work or test-only patch.
|
|
66
66
|
- full acceptance checklist path or pasted text.
|
|
67
67
|
- acceptance items or AC IDs.
|
|
68
|
-
- each Acceptance Checklist item delivery fields: `acceptance_scope` (`system_capability_build`, `representative_sample_validation`, `full_population_operation` or `full_population_not_required`), `ac_validates`, `ac_does_not_validate`, `sample_boundary` and `full_population_required`.
|
|
69
|
-
- required evidence and verification method per AC.
|
|
70
|
-
- required tests or explicit no-test scope.
|
|
71
|
-
- valid and invalid evidence rules.
|
|
72
|
-
- Completion State Machine rules.
|
|
73
|
-
- UI-Facing Gate rules when any AC touches UI.
|
|
74
|
-
- hard blockers or an explicit no-blocker statement.
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
- each Acceptance Checklist item delivery fields: `acceptance_scope` (`system_capability_build`, `representative_sample_validation`, `full_population_operation` or `full_population_not_required`), `ac_validates`, `ac_does_not_validate`, `sample_boundary` and `full_population_required`.
|
|
69
|
+
- required evidence and verification method per AC.
|
|
70
|
+
- required tests or explicit no-test scope.
|
|
71
|
+
- valid and invalid evidence rules.
|
|
72
|
+
- Completion State Machine rules.
|
|
73
|
+
- UI-Facing Gate rules when any AC touches UI.
|
|
74
|
+
- hard blockers or an explicit no-blocker statement.
|
|
75
|
+
|
|
76
|
+
Strict input grammar:
|
|
77
|
+
|
|
78
|
+
- Product / Architecture Source is a single document-level object. Its delivery fields must use fixed `key: value`, indented-list or `key: |` syntax.
|
|
79
|
+
- Technical Realization Plan items are definitions only when written as Markdown headings such as `## PI-001: ...` or `### PI-001: ...`.
|
|
80
|
+
- Acceptance Checklist items are definitions only when written as Markdown headings such as `## AC-001: ...` or `### AC-001: ...`.
|
|
81
|
+
- PI/AC fields are read only from the heading section until the next same-or-higher heading, and must use fixed `key: value`, indented-list or `key: |` syntax.
|
|
82
|
+
- Plain prose, mapping previews, tables, `related_acs`, `related_plan_items` and ordinary lists that mention `PI-001` or `AC-001` are references, not definitions.
|
|
83
|
+
- Legacy list-style definitions such as `- PI-001: ...` or `- AC-001: ...` followed by delivery fields are invalid. Stop and report the source file/line instead of accepting or repairing them.
|
|
84
|
+
|
|
85
|
+
If any of these are missing required fields, stop. Do not generate the Superpowers target-mode prompt. Report whether each missing field belongs in Product / Architecture Source, Technical Realization Plan, Acceptance Checklist, blocker section or Context reference. If the user supplied only a product/architecture source plus checklist, report missing Technical Realization Plan.
|
|
77
86
|
|
|
78
87
|
When blocked by missing input, return a structured Missing Fields Report with:
|
|
79
88
|
|
|
@@ -460,8 +469,9 @@ The local audit is process recovery only. It must not contain completion judgmen
|
|
|
460
469
|
- The prompt must state that Tiny Context gates wrap Superpowers for source authority, conformance and acceptance, but do not redefine or fork Superpowers execution mechanics.
|
|
461
470
|
- The prompt must identify the Superpowers workdir, Product / Architecture Source, Technical Realization Plan, Acceptance Checklist, `task-state.json`, `events.ndjson` and generated `derived/**` paths at the top.
|
|
462
471
|
- The prompt must state that the Technical Realization Plan controls plan conformance, the Product / Architecture Source prevents scope shrinkage and the full checklist controls acceptance.
|
|
463
|
-
- The prompt must state the capability-first delivery boundary: source/plan/AC fields distinguish system capability build, representative sample validation, full population operation and out-of-scope backlog; `scope_conflict_requires_decision` blocks completion; samples or framework-only evidence cannot prove full-population completion unless the AC explicitly allows it.
|
|
464
|
-
- The prompt must state
|
|
472
|
+
- The prompt must state the capability-first delivery boundary: source/plan/AC fields distinguish system capability build, representative sample validation, full population operation and out-of-scope backlog; `scope_conflict_requires_decision` blocks completion; samples or framework-only evidence cannot prove full-population completion unless the AC explicitly allows it.
|
|
473
|
+
- The prompt must state that compile has a strict input grammar: PI and AC definitions are heading sections only, while ordinary PI/AC mentions in prose, lists, tables, mapping previews or `related_*` fields are references.
|
|
474
|
+
- The prompt must state the Authority Model and that state/derived views/validator/auditor artifacts cannot rewrite source, plan or checklist authority.
|
|
465
475
|
- The prompt must require Product Context Delta and Technical Context Delta evaluation before implementation.
|
|
466
476
|
- The prompt must use parent-level Context Delta plus slice-level new durable fact checks.
|
|
467
477
|
- The prompt must state that Evidence Ledger / proof index is a generated execution index, but complete rows and ACs require evidence traceability to fresh evidence through `task-state.evidence[]` and optional `evidence_id`.
|
|
@@ -538,8 +548,8 @@ Tiny Context adapter for Superpowers; aligned to official skills, not upstream s
|
|
|
538
548
|
Superpowers input packet:
|
|
539
549
|
- Source guards scope; plan controls matrix; checklist controls verdict; task-state is execution state and derived views are generated only.
|
|
540
550
|
- Read Context/tests first; map gaps to test/API/UI/runtime/browser evidence.
|
|
541
|
-
Authority: source/plan/checklist own scope/plan/acceptance; state/derived/validator/auditor cannot rewrite. Complete rows need task-state.evidence[]/evidence_id. Goal mode: implementation/execution complete only when final-gate computes product_goal_complete=true. Read-only audit may end at audit_task_complete; non-accepted verdict says "Audit workflow completed; acceptance target not complete."; no bare "Goal achieved" or update_goal complete.
|
|
542
|
-
Delivery scope: source/plan/AC distinguish capability, samples, full population and backlog; scope_conflict_requires_decision blocks. Samples/framework-only evidence cannot prove full population unless AC allows.
|
|
551
|
+
Authority: source/plan/checklist own scope/plan/acceptance; state/derived/validator/auditor cannot rewrite. Complete rows need task-state.evidence[]/evidence_id. Goal mode: implementation/execution complete only when final-gate computes product_goal_complete=true. Read-only audit may end at audit_task_complete; non-accepted verdict says "Audit workflow completed; acceptance target not complete."; no bare "Goal achieved" or update_goal complete.
|
|
552
|
+
Delivery scope: source/plan/AC distinguish capability, samples, full population and backlog; scope_conflict_requires_decision blocks. Samples/framework-only evidence cannot prove full population unless AC allows.
|
|
543
553
|
|
|
544
554
|
Execution order:
|
|
545
555
|
1. Read inputs/Context. Task Contract: Product Context Delta none|required; Technical Context Delta none|required; any required -> Context Delta required. Not a validator gate.
|
|
@@ -553,8 +563,8 @@ Execution order:
|
|
|
553
563
|
9. Acceptance Evidence Gate: checklist controls verdict; each AC records proof chain, fresh evidence, missing layers, drift and substitution. Current contradictions override old passes.
|
|
554
564
|
10. Final gate: derive all -> verification-before-completion -> validate-superpowers-state -> validate-plan-acceptance -> auditor -> stale/overclaim scan -> superpowers final-gate. Auditor summary is not proof; rerun derive plus both validators after changes.
|
|
555
565
|
|
|
556
|
-
Autonomy/blockers: self-serve under current permissions. Open app/browser/CLI/settings and reuse sessions/auth/helpers. Try authorized sudo/gsudo/admin. Pause only after missing login/session expiry/denied permission/MFA/approval; give page/system/field.
|
|
557
|
-
Never complete on: audit/summary/final card, code/plan-only, partial/stale/sampled evidence, framework-only population proof, sample-as-full, missing layer, material drift, unapproved substitution/scope narrowing, unexercised runtime, unaccepted artifact, API/UI not reflected, missing validator pass or current API/UI/data/runtime/test contradiction.
|
|
566
|
+
Autonomy/blockers: self-serve under current permissions. Open app/browser/CLI/settings and reuse sessions/auth/helpers. Try authorized sudo/gsudo/admin. Pause only after missing login/session expiry/denied permission/MFA/approval; give page/system/field.
|
|
567
|
+
Never complete on: audit/summary/final card, code/plan-only, partial/stale/sampled evidence, framework-only population proof, sample-as-full, missing layer, material drift, unapproved substitution/scope narrowing, unexercised runtime, unaccepted artifact, API/UI not reflected, missing validator pass or current API/UI/data/runtime/test contradiction.
|
|
558
568
|
```
|
|
559
569
|
|
|
560
570
|
Before final response, check the prompt length. If it exceeds 3850 characters, tighten wording while preserving paths, input roles, official Superpowers skill names, Product Context Delta, Technical Context Delta, plan-conformance matrix, final verdict, state machine, UI gate, blockers and invalid evidence.
|
|
@@ -2,8 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { readText } from "./fs.js";
|
|
3
3
|
import { appendSuperpowersEvent } from "./superpowers-task-events.js";
|
|
4
4
|
import { loadSuperpowersState, recomputeStatuses, saveSuperpowersState, refreshSourceHashes } from "./superpowers-task-state.js";
|
|
5
|
-
import {
|
|
6
|
-
const DEFAULT_LAYERS = ["code", "test"];
|
|
5
|
+
import { DEFAULT_LAYERS, parseAcceptanceCriteria, parsePlanItems, parseProductArchitectureScope } from "./superpowers-task-source-compile.js";
|
|
7
6
|
export async function compileSuperpowersTask(workdir) {
|
|
8
7
|
const state = await loadSuperpowersState(workdir);
|
|
9
8
|
await refreshSourceHashes(workdir, state);
|
|
@@ -11,11 +10,11 @@ export async function compileSuperpowersTask(workdir) {
|
|
|
11
10
|
const technicalPlan = await readText(path.join(workdir, state.sources.technical_realization_plan.path));
|
|
12
11
|
const checklist = await readText(path.join(workdir, state.sources.acceptance_checklist.path));
|
|
13
12
|
state.delivery = {
|
|
14
|
-
product_architecture_scope: parseProductArchitectureScope(productSource),
|
|
13
|
+
product_architecture_scope: parseProductArchitectureScope(productSource, state.sources.product_architecture_source.path),
|
|
15
14
|
scope_conflicts: []
|
|
16
15
|
};
|
|
17
|
-
const planItems = parsePlanItems(technicalPlan);
|
|
18
|
-
const acceptanceCriteria = parseAcceptanceCriteria(checklist);
|
|
16
|
+
const planItems = parsePlanItems(technicalPlan, state.sources.technical_realization_plan.path);
|
|
17
|
+
const acceptanceCriteria = parseAcceptanceCriteria(checklist, state.sources.acceptance_checklist.path);
|
|
19
18
|
const acIds = Object.keys(acceptanceCriteria);
|
|
20
19
|
for (const [planId, item] of Object.entries(planItems)) {
|
|
21
20
|
if (item.related_acs.length === 0) {
|
|
@@ -47,90 +46,6 @@ export async function compileSuperpowersTask(workdir) {
|
|
|
47
46
|
});
|
|
48
47
|
return state;
|
|
49
48
|
}
|
|
50
|
-
function parseProductArchitectureScope(content) {
|
|
51
|
-
return {
|
|
52
|
-
delivery_scope: fieldText(content, "delivery_scope"),
|
|
53
|
-
full_population_required: fieldBoolean(content, "full_population_required"),
|
|
54
|
-
representative_samples_validate: field(content, "representative_samples_validate"),
|
|
55
|
-
representative_samples_do_not_validate: field(content, "representative_samples_do_not_validate"),
|
|
56
|
-
out_of_scope_backlog: field(content, "out_of_scope_backlog")
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function parsePlanItems(content) {
|
|
60
|
-
const items = {};
|
|
61
|
-
const matches = [...content.matchAll(/\b(PI-\d{3,})\b\s*[:.-]?\s*([^\n]*)/gi)];
|
|
62
|
-
for (const [index, match] of matches.entries()) {
|
|
63
|
-
const id = match[1].toUpperCase();
|
|
64
|
-
const block = blockAfter(content, match.index ?? 0, matches[index + 1]?.index);
|
|
65
|
-
items[id] = {
|
|
66
|
-
requirement: cleanText(match[2]) || firstLine(block) || id,
|
|
67
|
-
delivery_scope: fieldText(block, "delivery_scope"),
|
|
68
|
-
capability_target: fieldText(block, "capability_target"),
|
|
69
|
-
representative_samples: field(block, "representative_samples"),
|
|
70
|
-
full_population_boundary: fieldText(block, "full_population_boundary"),
|
|
71
|
-
non_required_population: field(block, "non_required_population"),
|
|
72
|
-
owner_surfaces: field(block, "owner_surfaces"),
|
|
73
|
-
forbidden_surfaces: field(block, "forbidden_surfaces"),
|
|
74
|
-
implementation_paths: field(block, "implementation_paths"),
|
|
75
|
-
required_tests: field(block, "required_tests"),
|
|
76
|
-
status: "not_started",
|
|
77
|
-
related_acs: field(block, "related_acs").map((item) => item.toUpperCase()),
|
|
78
|
-
required_proof_layers: []
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
if (Object.keys(items).length === 0) {
|
|
82
|
-
items["PI-001"] = {
|
|
83
|
-
requirement: firstLine(content) || "Implement technical realization plan",
|
|
84
|
-
delivery_scope: "",
|
|
85
|
-
capability_target: "",
|
|
86
|
-
representative_samples: [],
|
|
87
|
-
full_population_boundary: "",
|
|
88
|
-
non_required_population: [],
|
|
89
|
-
owner_surfaces: [],
|
|
90
|
-
forbidden_surfaces: [],
|
|
91
|
-
implementation_paths: [],
|
|
92
|
-
required_tests: [],
|
|
93
|
-
status: "not_started",
|
|
94
|
-
related_acs: [],
|
|
95
|
-
required_proof_layers: []
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
return items;
|
|
99
|
-
}
|
|
100
|
-
function parseAcceptanceCriteria(content) {
|
|
101
|
-
const items = {};
|
|
102
|
-
const matches = [...content.matchAll(/\b(AC-\d{3,})\b\s*[:.-]?\s*([^\n]*)/gi)];
|
|
103
|
-
for (const [index, match] of matches.entries()) {
|
|
104
|
-
const id = match[1].toUpperCase();
|
|
105
|
-
const block = blockAfter(content, match.index ?? 0, matches[index + 1]?.index);
|
|
106
|
-
const layers = field(block, "required_proof_layers").map(normalizeLayer).filter(Boolean);
|
|
107
|
-
items[id] = {
|
|
108
|
-
scope: cleanText(match[2]) || firstLine(block) || id,
|
|
109
|
-
acceptance_scope: fieldText(block, "acceptance_scope"),
|
|
110
|
-
ac_validates: field(block, "ac_validates"),
|
|
111
|
-
ac_does_not_validate: field(block, "ac_does_not_validate"),
|
|
112
|
-
sample_boundary: fieldText(block, "sample_boundary"),
|
|
113
|
-
full_population_required: fieldBoolean(block, "full_population_required"),
|
|
114
|
-
related_plan_items: field(block, "related_plan_items").map((item) => item.toUpperCase()),
|
|
115
|
-
required_proof_layers: layers.length > 0 ? layers : DEFAULT_LAYERS,
|
|
116
|
-
status: "not_run"
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
if (Object.keys(items).length === 0) {
|
|
120
|
-
items["AC-001"] = {
|
|
121
|
-
scope: firstLine(content) || "Acceptance checklist item",
|
|
122
|
-
acceptance_scope: "",
|
|
123
|
-
ac_validates: [],
|
|
124
|
-
ac_does_not_validate: [],
|
|
125
|
-
sample_boundary: "",
|
|
126
|
-
full_population_required: null,
|
|
127
|
-
related_plan_items: [],
|
|
128
|
-
required_proof_layers: DEFAULT_LAYERS,
|
|
129
|
-
status: "not_run"
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
return items;
|
|
133
|
-
}
|
|
134
49
|
export function computeScopeConflicts(state) {
|
|
135
50
|
const conflicts = [];
|
|
136
51
|
const product = state.delivery?.product_architecture_scope;
|
|
@@ -187,37 +102,6 @@ function compileProgress(state) {
|
|
|
187
102
|
}
|
|
188
103
|
};
|
|
189
104
|
}
|
|
190
|
-
function blockAfter(content, start, end) {
|
|
191
|
-
return content.slice(start, end ?? content.length);
|
|
192
|
-
}
|
|
193
|
-
function field(block, name) {
|
|
194
|
-
const text = fieldText(block, name);
|
|
195
|
-
return text ? asStringArray(text) : [];
|
|
196
|
-
}
|
|
197
|
-
function fieldText(block, name) {
|
|
198
|
-
const pattern = new RegExp(`${name}\\s*:\\s*([^\\n]+)`, "i");
|
|
199
|
-
const match = pattern.exec(block);
|
|
200
|
-
return match ? cleanText(match[1]) : "";
|
|
201
|
-
}
|
|
202
|
-
function fieldBoolean(block, name) {
|
|
203
|
-
const value = fieldText(block, name).toLowerCase();
|
|
204
|
-
if (value === "true") {
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
if (value === "false") {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
function normalizeLayer(value) {
|
|
213
|
-
return value.trim().toLowerCase().replace(/[- ]+/g, "_");
|
|
214
|
-
}
|
|
215
|
-
function firstLine(content) {
|
|
216
|
-
return cleanText(content.split(/\r?\n/).find((line) => cleanText(line)) ?? "");
|
|
217
|
-
}
|
|
218
|
-
function cleanText(value) {
|
|
219
|
-
return value.replace(/^[-#*\s]+/, "").trim();
|
|
220
|
-
}
|
|
221
105
|
function unique(values) {
|
|
222
106
|
return [...new Set(values.filter(Boolean))];
|
|
223
107
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type SuperpowersAcceptanceCriterion, type SuperpowersPlanItem, type SuperpowersProductArchitectureScope } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export declare const DEFAULT_LAYERS: string[];
|
|
3
|
+
export declare function parseProductArchitectureScope(content: string, sourceFile: string): SuperpowersProductArchitectureScope;
|
|
4
|
+
export declare function parsePlanItems(content: string, sourceFile: string): Record<string, SuperpowersPlanItem>;
|
|
5
|
+
export declare function parseAcceptanceCriteria(content: string, sourceFile: string): Record<string, SuperpowersAcceptanceCriterion>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { fieldArray, fieldBoolean, fieldLine, fieldText, parseDocumentFields, parseHeadingDefinitions } from "./superpowers-task-source-parser.js";
|
|
2
|
+
export const DEFAULT_LAYERS = ["code", "test"];
|
|
3
|
+
const PRODUCT_DELIVERY_SCOPES = new Set(["system_capability_build", "representative_sample_validation", "full_population_operation", "mixed_scope_requires_boundary"]);
|
|
4
|
+
const PLAN_DELIVERY_SCOPES = new Set(["system_capability_build", "representative_sample_validation", "full_population_operation", "out_of_scope_backlog"]);
|
|
5
|
+
const ACCEPTANCE_SCOPES = new Set(["system_capability_build", "representative_sample_validation", "full_population_operation", "full_population_not_required"]);
|
|
6
|
+
const PRODUCT_FIELDS = new Set([
|
|
7
|
+
"delivery_scope",
|
|
8
|
+
"full_population_required",
|
|
9
|
+
"representative_samples_validate",
|
|
10
|
+
"representative_samples_do_not_validate",
|
|
11
|
+
"out_of_scope_backlog"
|
|
12
|
+
]);
|
|
13
|
+
const PLAN_FIELDS = new Set([
|
|
14
|
+
"delivery_scope",
|
|
15
|
+
"capability_target",
|
|
16
|
+
"representative_samples",
|
|
17
|
+
"full_population_boundary",
|
|
18
|
+
"non_required_population",
|
|
19
|
+
"owner_surfaces",
|
|
20
|
+
"forbidden_surfaces",
|
|
21
|
+
"implementation_paths",
|
|
22
|
+
"required_tests",
|
|
23
|
+
"related_acs"
|
|
24
|
+
]);
|
|
25
|
+
const ACCEPTANCE_FIELDS = new Set([
|
|
26
|
+
"acceptance_scope",
|
|
27
|
+
"ac_validates",
|
|
28
|
+
"ac_does_not_validate",
|
|
29
|
+
"sample_boundary",
|
|
30
|
+
"full_population_required",
|
|
31
|
+
"related_plan_items",
|
|
32
|
+
"required_proof_layers"
|
|
33
|
+
]);
|
|
34
|
+
export function parseProductArchitectureScope(content, sourceFile) {
|
|
35
|
+
const fields = parseDocumentFields(content, sourceFile, PRODUCT_FIELDS);
|
|
36
|
+
const errors = [];
|
|
37
|
+
const scope = {
|
|
38
|
+
delivery_scope: requireEnum(errors, "Product / Architecture Source", "delivery_scope", fields, PRODUCT_DELIVERY_SCOPES, sourceFile, 1),
|
|
39
|
+
full_population_required: requireBoolean(errors, "Product / Architecture Source", "full_population_required", fields, sourceFile, 1),
|
|
40
|
+
representative_samples_validate: requireArray(errors, "Product / Architecture Source", "representative_samples_validate", fields, sourceFile, 1),
|
|
41
|
+
representative_samples_do_not_validate: requireArray(errors, "Product / Architecture Source", "representative_samples_do_not_validate", fields, sourceFile, 1),
|
|
42
|
+
out_of_scope_backlog: requireArray(errors, "Product / Architecture Source", "out_of_scope_backlog", fields, sourceFile, 1)
|
|
43
|
+
};
|
|
44
|
+
throwCompileErrors(errors);
|
|
45
|
+
return scope;
|
|
46
|
+
}
|
|
47
|
+
export function parsePlanItems(content, sourceFile) {
|
|
48
|
+
const items = {};
|
|
49
|
+
const errors = [];
|
|
50
|
+
for (const definition of parseHeadingDefinitions(content, { kind: "PI", sourceFile, allowedFields: PLAN_FIELDS })) {
|
|
51
|
+
const fields = definition.fields;
|
|
52
|
+
items[definition.id] = {
|
|
53
|
+
requirement: definition.title,
|
|
54
|
+
source_file: definition.source_file,
|
|
55
|
+
source_start_line: definition.source_start_line,
|
|
56
|
+
source_end_line: definition.source_end_line,
|
|
57
|
+
delivery_scope: requireEnum(errors, definition.id, "delivery_scope", fields, PLAN_DELIVERY_SCOPES, sourceFile, definition.source_start_line),
|
|
58
|
+
capability_target: requireText(errors, definition.id, "capability_target", fields, sourceFile, definition.source_start_line),
|
|
59
|
+
representative_samples: requireArray(errors, definition.id, "representative_samples", fields, sourceFile, definition.source_start_line),
|
|
60
|
+
full_population_boundary: requireText(errors, definition.id, "full_population_boundary", fields, sourceFile, definition.source_start_line),
|
|
61
|
+
non_required_population: requireArray(errors, definition.id, "non_required_population", fields, sourceFile, definition.source_start_line),
|
|
62
|
+
owner_surfaces: optionalArray(fields, "owner_surfaces"),
|
|
63
|
+
forbidden_surfaces: optionalArray(fields, "forbidden_surfaces"),
|
|
64
|
+
implementation_paths: optionalArray(fields, "implementation_paths"),
|
|
65
|
+
required_tests: optionalArray(fields, "required_tests"),
|
|
66
|
+
status: "not_started",
|
|
67
|
+
related_acs: optionalArray(fields, "related_acs").map((item) => item.toUpperCase()),
|
|
68
|
+
required_proof_layers: []
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
throwCompileErrors(errors);
|
|
72
|
+
return items;
|
|
73
|
+
}
|
|
74
|
+
export function parseAcceptanceCriteria(content, sourceFile) {
|
|
75
|
+
const items = {};
|
|
76
|
+
const errors = [];
|
|
77
|
+
for (const definition of parseHeadingDefinitions(content, { kind: "AC", sourceFile, allowedFields: ACCEPTANCE_FIELDS })) {
|
|
78
|
+
const fields = definition.fields;
|
|
79
|
+
const layers = optionalArray(fields, "required_proof_layers").map(normalizeLayer).filter(Boolean);
|
|
80
|
+
items[definition.id] = {
|
|
81
|
+
scope: definition.title,
|
|
82
|
+
source_file: definition.source_file,
|
|
83
|
+
source_start_line: definition.source_start_line,
|
|
84
|
+
source_end_line: definition.source_end_line,
|
|
85
|
+
acceptance_scope: requireEnum(errors, definition.id, "acceptance_scope", fields, ACCEPTANCE_SCOPES, sourceFile, definition.source_start_line),
|
|
86
|
+
ac_validates: requireArray(errors, definition.id, "ac_validates", fields, sourceFile, definition.source_start_line),
|
|
87
|
+
ac_does_not_validate: requireArray(errors, definition.id, "ac_does_not_validate", fields, sourceFile, definition.source_start_line),
|
|
88
|
+
sample_boundary: requireText(errors, definition.id, "sample_boundary", fields, sourceFile, definition.source_start_line),
|
|
89
|
+
full_population_required: requireBoolean(errors, definition.id, "full_population_required", fields, sourceFile, definition.source_start_line),
|
|
90
|
+
related_plan_items: optionalArray(fields, "related_plan_items").map((item) => item.toUpperCase()),
|
|
91
|
+
required_proof_layers: layers.length > 0 ? layers : DEFAULT_LAYERS,
|
|
92
|
+
status: "not_run"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
throwCompileErrors(errors);
|
|
96
|
+
return items;
|
|
97
|
+
}
|
|
98
|
+
function normalizeLayer(value) {
|
|
99
|
+
return value.trim().toLowerCase().replace(/[- ]+/g, "_");
|
|
100
|
+
}
|
|
101
|
+
function requireEnum(errors, label, name, fields, allowed, sourceFile, fallbackLine) {
|
|
102
|
+
const value = fieldText(fields, name);
|
|
103
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
104
|
+
if (!value) {
|
|
105
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
if (!allowed.has(value)) {
|
|
109
|
+
errors.push(`${label} invalid ${name}: ${value} at ${sourceFile}:${line}; allowed: ${[...allowed].join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
function requireText(errors, label, name, fields, sourceFile, fallbackLine) {
|
|
114
|
+
const value = fieldText(fields, name);
|
|
115
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
116
|
+
if (!value) {
|
|
117
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
function requireArray(errors, label, name, fields, sourceFile, fallbackLine) {
|
|
122
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
123
|
+
if (!fields[name]) {
|
|
124
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
return fieldArray(fields, name);
|
|
128
|
+
}
|
|
129
|
+
function requireBoolean(errors, label, name, fields, sourceFile, fallbackLine) {
|
|
130
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
131
|
+
if (!fields[name]) {
|
|
132
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const value = fieldBoolean(fields, name);
|
|
136
|
+
if (value === null) {
|
|
137
|
+
errors.push(`${label} invalid ${name}: ${fieldText(fields, name)} at ${sourceFile}:${line}; must be true or false`);
|
|
138
|
+
}
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
function optionalArray(fields, name) {
|
|
142
|
+
return fields[name] ? fieldArray(fields, name) : [];
|
|
143
|
+
}
|
|
144
|
+
function throwCompileErrors(errors) {
|
|
145
|
+
if (errors.length > 0) {
|
|
146
|
+
throw new Error(`Superpowers source compile failed:\n- ${errors.join("\n- ")}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ParsedField {
|
|
2
|
+
value: string | string[];
|
|
3
|
+
line: number;
|
|
4
|
+
}
|
|
5
|
+
export interface ParsedDefinition {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
fields: Record<string, ParsedField>;
|
|
9
|
+
source_file: string;
|
|
10
|
+
source_start_line: number;
|
|
11
|
+
source_end_line: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DefinitionParseOptions {
|
|
14
|
+
kind: "PI" | "AC";
|
|
15
|
+
sourceFile: string;
|
|
16
|
+
allowedFields: Set<string>;
|
|
17
|
+
}
|
|
18
|
+
export declare function parseHeadingDefinitions(content: string, options: DefinitionParseOptions): ParsedDefinition[];
|
|
19
|
+
export declare function parseDocumentFields(content: string, sourceFile: string, allowedFields: Set<string>): Record<string, ParsedField>;
|
|
20
|
+
export declare function fieldText(fields: Record<string, ParsedField>, name: string): string;
|
|
21
|
+
export declare function fieldArray(fields: Record<string, ParsedField>, name: string): string[];
|
|
22
|
+
export declare function fieldBoolean(fields: Record<string, ParsedField>, name: string): boolean | null;
|
|
23
|
+
export declare function fieldLine(fields: Record<string, ParsedField>, name: string): number | undefined;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export function parseHeadingDefinitions(content, options) {
|
|
2
|
+
const lines = splitLines(content);
|
|
3
|
+
const errors = [];
|
|
4
|
+
rejectListStyleDefinitions(lines, options, errors);
|
|
5
|
+
const headings = findDefinitionHeadings(lines, options.kind);
|
|
6
|
+
const seen = new Map();
|
|
7
|
+
const definitions = [];
|
|
8
|
+
for (const heading of headings) {
|
|
9
|
+
const firstLine = seen.get(heading.id);
|
|
10
|
+
if (firstLine !== undefined) {
|
|
11
|
+
errors.push(`${heading.id} duplicate definition at ${options.sourceFile}:${firstLine} and ${options.sourceFile}:${heading.line}`);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
seen.set(heading.id, heading.line);
|
|
15
|
+
const sourceEndLine = sectionEndLine(lines, heading.index, heading.level);
|
|
16
|
+
const section = lines.slice(heading.index, sourceEndLine);
|
|
17
|
+
definitions.push({
|
|
18
|
+
id: heading.id,
|
|
19
|
+
title: heading.title,
|
|
20
|
+
fields: parseFields(section, options.sourceFile, heading.line, options.allowedFields, errors),
|
|
21
|
+
source_file: options.sourceFile,
|
|
22
|
+
source_start_line: heading.line,
|
|
23
|
+
source_end_line: sourceEndLine
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (definitions.length === 0) {
|
|
27
|
+
errors.push(`${options.sourceFile} must define ${options.kind} items with Markdown headings like "## ${options.kind}-001: ..."`);
|
|
28
|
+
}
|
|
29
|
+
throwIfErrors(errors);
|
|
30
|
+
return definitions;
|
|
31
|
+
}
|
|
32
|
+
export function parseDocumentFields(content, sourceFile, allowedFields) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
const fields = parseFields(splitLines(content), sourceFile, 1, allowedFields, errors);
|
|
35
|
+
throwIfErrors(errors);
|
|
36
|
+
return fields;
|
|
37
|
+
}
|
|
38
|
+
export function fieldText(fields, name) {
|
|
39
|
+
const field = fields[name];
|
|
40
|
+
if (!field) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
return Array.isArray(field.value) ? field.value.join("\n").trim() : field.value.trim();
|
|
44
|
+
}
|
|
45
|
+
export function fieldArray(fields, name) {
|
|
46
|
+
const field = fields[name];
|
|
47
|
+
if (!field) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(field.value)) {
|
|
51
|
+
return field.value.flatMap(splitScalarArray).filter(Boolean);
|
|
52
|
+
}
|
|
53
|
+
return splitScalarArray(field.value);
|
|
54
|
+
}
|
|
55
|
+
export function fieldBoolean(fields, name) {
|
|
56
|
+
const value = fieldText(fields, name).toLowerCase();
|
|
57
|
+
if (value === "true") {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (value === "false") {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function fieldLine(fields, name) {
|
|
66
|
+
return fields[name]?.line;
|
|
67
|
+
}
|
|
68
|
+
function parseFields(lines, sourceFile, startLine, allowedFields, errors) {
|
|
69
|
+
const fields = {};
|
|
70
|
+
for (let index = 0; index < lines.length; index++) {
|
|
71
|
+
const line = lines[index];
|
|
72
|
+
const lineNumber = startLine + index;
|
|
73
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
74
|
+
if (heading && allowedFields.has(heading[2].trim())) {
|
|
75
|
+
errors.push(`${sourceFile}:${lineNumber} field headings are not supported; use "${heading[2].trim()}: ..."`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (/^\s*\|/.test(line) && containsKnownField(line, allowedFields)) {
|
|
79
|
+
errors.push(`${sourceFile}:${lineNumber} table fields are not supported; use key: value fields`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const match = /^([a-z][a-z0-9_]*)\s*:\s*(.*)$/.exec(line);
|
|
83
|
+
if (!match) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const name = match[1];
|
|
87
|
+
if (!allowedFields.has(name)) {
|
|
88
|
+
if (name.includes("_")) {
|
|
89
|
+
errors.push(`${sourceFile}:${lineNumber} unknown field ${name}`);
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (fields[name]) {
|
|
94
|
+
errors.push(`${sourceFile}:${lineNumber} duplicate field ${name}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const parsed = parseFieldValue(lines, index, match[2], sourceFile, lineNumber, errors);
|
|
98
|
+
fields[name] = { value: parsed.value, line: lineNumber };
|
|
99
|
+
index = parsed.endIndex;
|
|
100
|
+
}
|
|
101
|
+
return fields;
|
|
102
|
+
}
|
|
103
|
+
function parseFieldValue(lines, index, rest, sourceFile, lineNumber, errors) {
|
|
104
|
+
const trimmed = rest.trim();
|
|
105
|
+
if (trimmed === "|") {
|
|
106
|
+
const block = [];
|
|
107
|
+
let cursor = index + 1;
|
|
108
|
+
for (; cursor < lines.length; cursor++) {
|
|
109
|
+
const next = lines[cursor];
|
|
110
|
+
if (next.trim() && !/^\s/.test(next)) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
block.push(next.replace(/^\s{0,2}/, ""));
|
|
114
|
+
}
|
|
115
|
+
if (block.length === 0) {
|
|
116
|
+
errors.push(`${sourceFile}:${lineNumber} block field must include indented text`);
|
|
117
|
+
}
|
|
118
|
+
return { value: block.join("\n").trim(), endIndex: cursor - 1 };
|
|
119
|
+
}
|
|
120
|
+
if (trimmed) {
|
|
121
|
+
return { value: cleanValue(trimmed), endIndex: index };
|
|
122
|
+
}
|
|
123
|
+
const values = [];
|
|
124
|
+
let cursor = index + 1;
|
|
125
|
+
for (; cursor < lines.length; cursor++) {
|
|
126
|
+
const next = lines[cursor];
|
|
127
|
+
if (!next.trim()) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!/^\s/.test(next)) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
const listItem = /^\s*[-*+]\s+(.+?)\s*$/.exec(next);
|
|
134
|
+
if (!listItem) {
|
|
135
|
+
errors.push(`${sourceFile}:${cursor + 1} field lists must use indented "- item" entries or key: | blocks`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
values.push(cleanValue(listItem[1]));
|
|
139
|
+
}
|
|
140
|
+
return { value: values, endIndex: cursor - 1 };
|
|
141
|
+
}
|
|
142
|
+
function rejectListStyleDefinitions(lines, options, errors) {
|
|
143
|
+
const idPattern = options.kind === "PI" ? "PI" : "AC";
|
|
144
|
+
const listPattern = new RegExp(`^\\s*[-*+]\\s+(${idPattern}-\\d{3,})\\b\\s*[:.-]?`, "i");
|
|
145
|
+
for (let index = 0; index < lines.length; index++) {
|
|
146
|
+
const match = listPattern.exec(lines[index]);
|
|
147
|
+
if (!match) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const block = listItemBlock(lines, index);
|
|
151
|
+
if (!block.some((line) => containsKnownField(line, options.allowedFields))) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const id = match[1].toUpperCase();
|
|
155
|
+
errors.push(`${id} list-style definition is not allowed at ${options.sourceFile}:${index + 1}; use "## ${id}: ..."`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function listItemBlock(lines, start) {
|
|
159
|
+
const block = [lines[start]];
|
|
160
|
+
for (let index = start + 1; index < lines.length; index++) {
|
|
161
|
+
const line = lines[index];
|
|
162
|
+
if (line.trim() && !/^\s/.test(line)) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
block.push(line);
|
|
166
|
+
}
|
|
167
|
+
return block;
|
|
168
|
+
}
|
|
169
|
+
function findDefinitionHeadings(lines, kind) {
|
|
170
|
+
const pattern = new RegExp(`^(#{1,6})\\s+(${kind}-\\d{3,})\\b(?:\\s*[:.-]\\s*|\\s+)?(.*?)\\s*$`, "i");
|
|
171
|
+
return lines.flatMap((line, index) => {
|
|
172
|
+
const match = pattern.exec(line);
|
|
173
|
+
if (!match) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
return [{
|
|
177
|
+
id: match[2].toUpperCase(),
|
|
178
|
+
level: match[1].length,
|
|
179
|
+
title: cleanValue(match[3]) || match[2].toUpperCase(),
|
|
180
|
+
index,
|
|
181
|
+
line: index + 1
|
|
182
|
+
}];
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function sectionEndLine(lines, headingIndex, headingLevel) {
|
|
186
|
+
for (let index = headingIndex + 1; index < lines.length; index++) {
|
|
187
|
+
const match = /^(#{1,6})\s+/.exec(lines[index]);
|
|
188
|
+
if (match && match[1].length <= headingLevel) {
|
|
189
|
+
return index;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return lines.length;
|
|
193
|
+
}
|
|
194
|
+
function containsKnownField(line, allowedFields) {
|
|
195
|
+
for (const field of allowedFields) {
|
|
196
|
+
if (new RegExp(`\\b${field}\\b\\s*:`, "i").test(line) || new RegExp(`\\b${field}\\b`, "i").test(line)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
function splitScalarArray(value) {
|
|
203
|
+
return value
|
|
204
|
+
.split(/[,;\n]/)
|
|
205
|
+
.map((item) => cleanValue(item))
|
|
206
|
+
.filter(Boolean);
|
|
207
|
+
}
|
|
208
|
+
function splitLines(content) {
|
|
209
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
210
|
+
}
|
|
211
|
+
function cleanValue(value) {
|
|
212
|
+
return value.replace(/^[-#*\s]+/, "").trim();
|
|
213
|
+
}
|
|
214
|
+
function throwIfErrors(errors) {
|
|
215
|
+
if (errors.length > 0) {
|
|
216
|
+
throw new Error(`Superpowers source compile failed:\n- ${errors.join("\n- ")}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -122,6 +122,9 @@ export interface SuperpowersProgressState {
|
|
|
122
122
|
}
|
|
123
123
|
export interface SuperpowersPlanItem {
|
|
124
124
|
requirement: string;
|
|
125
|
+
source_file: string;
|
|
126
|
+
source_start_line: number;
|
|
127
|
+
source_end_line: number;
|
|
125
128
|
delivery_scope: SuperpowersPlanDeliveryScope | "";
|
|
126
129
|
capability_target: string;
|
|
127
130
|
representative_samples: string[];
|
|
@@ -137,6 +140,9 @@ export interface SuperpowersPlanItem {
|
|
|
137
140
|
}
|
|
138
141
|
export interface SuperpowersAcceptanceCriterion {
|
|
139
142
|
scope: string;
|
|
143
|
+
source_file: string;
|
|
144
|
+
source_start_line: number;
|
|
145
|
+
source_end_line: number;
|
|
140
146
|
acceptance_scope: SuperpowersAcceptanceScope | "";
|
|
141
147
|
ac_validates: string[];
|
|
142
148
|
ac_does_not_validate: string[];
|