@gobing-ai/spur 0.3.95 → 0.3.96
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/.claude-plugin/marketplace.json +1 -1
- package/config/plugin-scripts.json +0 -54
- package/config/rules/boundary/sp-script-placement.yaml +19 -0
- package/config/rules/strict/runtime-boundaries.yaml +5 -0
- package/config/rules/structure/test-location.yaml +2 -0
- package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
- package/config/rules/typescript/output-boundaries.yaml +15 -2
- package/config/script-placement-baseline.json +51 -0
- package/config/workflows/feature-verification.yaml +1 -1
- package/config/workflows/history-anatomy.yaml +23 -11
- package/config/workflows/idea-pipeline.yaml +80 -85
- package/config/workflows/pr-review.yaml +18 -9
- package/config/workflows/task-pipeline.yaml +42 -95
- package/config/workflows/wrapup-pipeline.yaml +89 -37
- package/package.json +1 -1
- package/plugins/sp/README.md +8 -10
- package/plugins/sp/agents/super-reviewer.md +43 -18
- package/plugins/sp/commands/dev-fixgha.md +83 -0
- package/plugins/sp/commands/dev-gitmsg.md +8 -6
- package/plugins/sp/commands/dev-gtd.md +2 -2
- package/plugins/sp/commands/dev-idea.md +22 -12
- package/plugins/sp/commands/dev-plan.md +8 -9
- package/plugins/sp/commands/dev-review.md +22 -13
- package/plugins/sp/commands/dev-verifyall.md +1 -1
- package/plugins/sp/commands/spur-init.md +2 -2
- package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +5 -4
- package/plugins/sp/lib/inline-run.generated.d.mts +11 -0
- package/plugins/sp/lib/inline-run.generated.mjs +25 -8
- package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
- package/plugins/sp/lib/quality-gate.generated.mjs +438 -0
- package/plugins/sp/lib/residual-scan.generated.d.mts +62 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +210 -0
- package/plugins/sp/lib/spur-bin.ts +36 -0
- package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
- package/plugins/sp/lib/step-profile.generated.mjs +174 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +20 -19
- package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
- package/plugins/sp/scripts/inline-run-setup.mjs +85 -320
- package/plugins/sp/scripts/inline-run-setup.ts +104 -667
- package/plugins/sp/scripts/quality-gate.mjs +34 -19
- package/plugins/sp/scripts/quality-gate.ts +12 -658
- package/plugins/sp/scripts/residual-scan.mjs +135 -156
- package/plugins/sp/scripts/residual-scan.ts +102 -499
- package/plugins/sp/scripts/script-root.mjs +5 -1
- package/plugins/sp/scripts/script-root.ts +5 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +25 -17
- package/plugins/sp/scripts/workflow-step-profile.ts +21 -315
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +8 -2
- package/plugins/sp/scripts/wrapup-drift-probe.ts +3 -2
- package/plugins/sp/scripts/wrapup-steps.mjs +10 -32
- package/plugins/sp/scripts/wrapup-steps.ts +8 -36
- package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
- package/plugins/sp/skills/code-verification/SKILL.md +34 -9
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +3 -3
- package/plugins/sp/skills/functional-review/SKILL.md +7 -4
- package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
- package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
- package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
- package/plugins/sp/skills/spur-cli/references/features.md +17 -6
- package/plugins/sp/skills/spur-cli/references/init.md +17 -16
- package/plugins/sp/skills/spur-cli/references/self.md +3 -2
- package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +21 -6
- package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +11 -9
- package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +86 -70
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +77 -34
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +5 -9
- package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +31 -21
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
- package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +29 -3
- package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
- package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
- package/spur.js +1495 -369
- package/web/_astro/{BoardApp.Cpxntzad.js → BoardApp.BIjMatT1.js} +1 -1
- package/web/_astro/{BoardApp.BxJuwD7I.js → BoardApp.GvjIe9Z6.js} +42 -42
- package/web/_astro/{TaskDetail.C5bW4WGV.js → TaskDetail.BZM3EAFx.js} +1 -1
- package/web/_astro/{arc.CyjRvNMY.js → arc.HkRiZnoI.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.B4lWRzJA.js → architectureDiagram-3BPJPVTR.BDdK-tgZ.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Be38USQ2.js → blockDiagram-GPEHLZMM.CZ9kOBxx.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DT9Fj5Qx.js → c4Diagram-AAUBKEIU.DXK9qeRD.js} +1 -1
- package/web/_astro/channel.DlhX2MEt.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.SeSBWLg5.js → chunk-2J33WTMH.BTD2WeX5.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DoO4VE14.js → chunk-4BX2VUAB.Ci6Qbcvk.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DrmTFA53.js → chunk-55IACEB6.yl3zsj7p.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Bi-TUb_V.js → chunk-727SXJPM.ClXZsfyR.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.B-xp8Uhi.js → chunk-AQP2D5EJ.BmKWZBcP.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DGWrDnUU.js → chunk-FMBD7UC4.By30fcb7.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM._BagvPDy.js → chunk-ND2GUHAM.24NmrD-k.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.VOboYWQ0.js → chunk-QZHKN3VN.BN4sKdcS.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.D_apfHS7.js → classDiagram-4FO5ZUOK.CJpoMPb5.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D_apfHS7.js → classDiagram-v2-Q7XG4LA2.CJpoMPb5.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DNLU_L-x.js → cose-bilkent-S5V4N54A.BraxQ2Nt.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.D8borhV-.js → cynefin-OW5HDTMX.gzoU73oL.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Uc0lvjcV.js → dagre-BM42HDAG.wyDfWySU.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DDIxtDBo.js → diagram-2AECGRRQ.h2_EbMxV.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.DVoDnD3H.js → diagram-5GNKFQAL.Db6dcs89.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CRubTJ-0.js → diagram-KO2AKTUF.NVwzxu_U.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.m9SeYcXQ.js → diagram-LMA3HP47.B-eNgDQk.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.C6s2ZjV5.js → diagram-OG6HWLK6.Bsa8ZRbJ.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.nSzr5KTj.js → erDiagram-TEJ5UH35.BospRE5Q.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DrZcys16.js → flowDiagram-I6XJVG4X.DMzvHwAA.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.Do4M1b-K.js → ganttDiagram-6RSMTGT7.Dmdxgd-M.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.BWc9oJ4l.js → gitGraphDiagram-PVQCEYII.CaqdO0rL.js} +1 -1
- package/web/_astro/index.EoZlzLK-.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.BIdOctFk.js → infoDiagram-5YYISTIA.DDVD9Wn7.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.CV5SuG2t.js → ishikawaDiagram-YF4QCWOH.EKyCUFsL.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CxE-rEJ-.js → journeyDiagram-JHISSGLW.By2-RkYB.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BPRdKWs_.js → kanban-definition-UN3LZRKU.ar-WmlyR.js} +1 -1
- package/web/_astro/{linear.zRsuuDTE.js → linear.BnzMBgo_.js} +1 -1
- package/web/_astro/{mermaid.core.jAJTcMKc.js → mermaid.core.DSqeFu2Y.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BffJxfCr.js → mindmap-definition-RKZ34NQL.1XcF8Wh2.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CacwWaE3.js → pieDiagram-4H26LBE5.DnItHOdK.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.hXELD06-.js → quadrantDiagram-W4KKPZXB.BQQyrnBt.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DdEbPcqT.js → requirementDiagram-4Y6WPE33.Dmzxun82.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DgaDocKv.js → sankeyDiagram-5OEKKPKP.BBIeRKYB.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B0KScnAu.js → sequenceDiagram-3UESZ5HK.CUNVdBBf.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CV9M_WNc.js → stateDiagram-AJRCARHV.DlFf2sJm.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.BOAo74Et.js → stateDiagram-v2-BHNVJYJU.DXR7I1H5.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.CqqepUMe.js → timeline-definition-PNZ67QCA.BcO-GyX2.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DDqe4dOG.js → vennDiagram-CIIHVFJN.ChCECmmW.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.B3R9OPdO.js → wardleyDiagram-YWT4CUSO.CRaT12mD.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.B3SvgXpQ.js → xychartDiagram-2RQKCTM6.B3lsHVpj.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
- package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
- package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -301
- package/plugins/sp/scripts/feature-sync-bounded.ts +0 -481
- package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
- package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
- package/plugins/sp/scripts/record-feature-sync.ts +0 -84
- package/plugins/sp/scripts/script-contract-check.ts +0 -506
- package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
- package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
- package/plugins/sp/scripts/task-evidence-precheck.ts +0 -189
- package/plugins/sp/scripts/task-size-precheck.ts +0 -212
- package/plugins/sp/scripts/transition-shim-check.ts +0 -238
- package/plugins/sp/scripts/validate-commands.ts +0 -689
- package/plugins/sp/scripts/validate-flag-contracts.ts +0 -890
- package/plugins/sp/scripts/verify-answer-lint.ts +0 -549
- package/web/_astro/channel.CI6N_tCg.js +0 -1
- package/web/_astro/index.CENnIEqT.css +0 -1
package/spur.js
CHANGED
|
@@ -14897,6 +14897,7 @@ var init_finding_codes = __esm(() => {
|
|
|
14897
14897
|
"L2.disallowed-section",
|
|
14898
14898
|
"L2.heading-level",
|
|
14899
14899
|
"L2.section-order",
|
|
14900
|
+
"L2.unclosed-code-fence",
|
|
14900
14901
|
"L3.requirements-format",
|
|
14901
14902
|
"L3.requirements-checkbox",
|
|
14902
14903
|
"L3.requirements-empty",
|
|
@@ -14951,7 +14952,10 @@ var init_finding_codes = __esm(() => {
|
|
|
14951
14952
|
"L4.feature-receipt-failed",
|
|
14952
14953
|
"L4.feature-receipt-run",
|
|
14953
14954
|
"L4.feature-receipt-stale",
|
|
14954
|
-
"L4.feature-receipt-contract"
|
|
14955
|
+
"L4.feature-receipt-contract",
|
|
14956
|
+
"precheck-size",
|
|
14957
|
+
"precheck-evidence",
|
|
14958
|
+
"inventory-coverage"
|
|
14955
14959
|
];
|
|
14956
14960
|
FINDING_CODES = {
|
|
14957
14961
|
L1_MARKDOWN_PARSE: "L1.markdown-parse",
|
|
@@ -14961,6 +14965,7 @@ var init_finding_codes = __esm(() => {
|
|
|
14961
14965
|
L2_DISALLOWED_SECTION: "L2.disallowed-section",
|
|
14962
14966
|
L2_HEADING_LEVEL: "L2.heading-level",
|
|
14963
14967
|
L2_SECTION_ORDER: "L2.section-order",
|
|
14968
|
+
L2_UNCLOSED_CODE_FENCE: "L2.unclosed-code-fence",
|
|
14964
14969
|
L3_REQUIREMENTS_FORMAT: "L3.requirements-format",
|
|
14965
14970
|
L3_REQUIREMENTS_CHECKBOX: "L3.requirements-checkbox",
|
|
14966
14971
|
L3_REQUIREMENTS_EMPTY: "L3.requirements-empty",
|
|
@@ -15015,7 +15020,10 @@ var init_finding_codes = __esm(() => {
|
|
|
15015
15020
|
L4_FEATURE_RECEIPT_FAILED: "L4.feature-receipt-failed",
|
|
15016
15021
|
L4_FEATURE_RECEIPT_RUN: "L4.feature-receipt-run",
|
|
15017
15022
|
L4_FEATURE_RECEIPT_STALE: "L4.feature-receipt-stale",
|
|
15018
|
-
L4_FEATURE_RECEIPT_CONTRACT: "L4.feature-receipt-contract"
|
|
15023
|
+
L4_FEATURE_RECEIPT_CONTRACT: "L4.feature-receipt-contract",
|
|
15024
|
+
PRECHECK_SIZE: "precheck-size",
|
|
15025
|
+
PRECHECK_EVIDENCE: "precheck-evidence",
|
|
15026
|
+
INVENTORY_COVERAGE: "inventory-coverage"
|
|
15019
15027
|
};
|
|
15020
15028
|
});
|
|
15021
15029
|
|
|
@@ -52233,6 +52241,10 @@ async function countToolCallsSince(db2, source, runStartedAt) {
|
|
|
52233
52241
|
const row = await db2.queryFirst("SELECT COUNT(*) AS cnt FROM history_tool_call WHERE source = ? AND imported_at >= ?", source, runStartedAt);
|
|
52234
52242
|
return row?.cnt ?? 0;
|
|
52235
52243
|
}
|
|
52244
|
+
async function countToolCallArgsRaw(db2, source) {
|
|
52245
|
+
const row = await db2.queryFirst("SELECT COUNT(*) AS cnt FROM history_tool_call WHERE args_raw IS NOT NULL AND source = ?", source);
|
|
52246
|
+
return row?.cnt ?? 0;
|
|
52247
|
+
}
|
|
52236
52248
|
function withStepPredicates(wmWhere, predicates) {
|
|
52237
52249
|
return wmWhere === "" ? `WHERE ${predicates}` : `${wmWhere} AND ${predicates}`;
|
|
52238
52250
|
}
|
|
@@ -73045,6 +73057,30 @@ function findHeadings(body, hashes) {
|
|
|
73045
73057
|
}
|
|
73046
73058
|
return hits;
|
|
73047
73059
|
}
|
|
73060
|
+
function findUnclosedFenceLine(text4) {
|
|
73061
|
+
let inCodeBlock = false;
|
|
73062
|
+
let openLine = 0;
|
|
73063
|
+
let lineNo = 0;
|
|
73064
|
+
let lineStart = 0;
|
|
73065
|
+
for (let i2 = 0;i2 <= text4.length; i2++) {
|
|
73066
|
+
const atEnd = i2 === text4.length;
|
|
73067
|
+
if (!atEnd && text4[i2] !== `
|
|
73068
|
+
`)
|
|
73069
|
+
continue;
|
|
73070
|
+
const line = text4.slice(lineStart, i2);
|
|
73071
|
+
lineNo++;
|
|
73072
|
+
if (line.startsWith("```")) {
|
|
73073
|
+
if (inCodeBlock) {
|
|
73074
|
+
inCodeBlock = false;
|
|
73075
|
+
} else {
|
|
73076
|
+
inCodeBlock = true;
|
|
73077
|
+
openLine = lineNo;
|
|
73078
|
+
}
|
|
73079
|
+
}
|
|
73080
|
+
lineStart = i2 + 1;
|
|
73081
|
+
}
|
|
73082
|
+
return inCodeBlock ? openLine : null;
|
|
73083
|
+
}
|
|
73048
73084
|
function normalizeYamlScalars(data) {
|
|
73049
73085
|
const out = {};
|
|
73050
73086
|
for (const [key, value] of Object.entries(data)) {
|
|
@@ -73067,12 +73103,14 @@ class MarkdownDocument {
|
|
|
73067
73103
|
_sections;
|
|
73068
73104
|
_demotedHeadings = [];
|
|
73069
73105
|
_duplicateSectionNames = [];
|
|
73070
|
-
|
|
73106
|
+
_unclosedFenceLine;
|
|
73107
|
+
constructor(domain2, frontmatterBlock, frontmatter, preamble, sections, unclosedFenceLine) {
|
|
73071
73108
|
this._domain = domain2;
|
|
73072
73109
|
this._frontmatterBlock = frontmatterBlock;
|
|
73073
73110
|
this._frontmatter = frontmatter;
|
|
73074
73111
|
this._preamble = preamble;
|
|
73075
73112
|
this._sections = sections;
|
|
73113
|
+
this._unclosedFenceLine = unclosedFenceLine;
|
|
73076
73114
|
}
|
|
73077
73115
|
static parse(content, domain2) {
|
|
73078
73116
|
const level = HEADING_LEVELS[domain2];
|
|
@@ -73095,6 +73133,8 @@ class MarkdownDocument {
|
|
|
73095
73133
|
frontmatterBlock = fmMatch[0];
|
|
73096
73134
|
body = content.slice(fmMatch[0].length);
|
|
73097
73135
|
}
|
|
73136
|
+
const bodyUnclosedFenceLine = findUnclosedFenceLine(body);
|
|
73137
|
+
const unclosedFenceLine = bodyUnclosedFenceLine === null ? null : bodyUnclosedFenceLine + (frontmatterBlock.match(/\n/g) ?? []).length;
|
|
73098
73138
|
const hits = findHeadings(body, hashes);
|
|
73099
73139
|
const first = hits[0];
|
|
73100
73140
|
const preamble = first !== undefined ? body.slice(0, first.index) : body;
|
|
@@ -73119,7 +73159,7 @@ class MarkdownDocument {
|
|
|
73119
73159
|
sections.push(s2);
|
|
73120
73160
|
}
|
|
73121
73161
|
}
|
|
73122
|
-
const doc2 = new MarkdownDocument(domain2, frontmatterBlock, frontmatter, preamble, sections);
|
|
73162
|
+
const doc2 = new MarkdownDocument(domain2, frontmatterBlock, frontmatter, preamble, sections, unclosedFenceLine);
|
|
73123
73163
|
doc2._duplicateSectionNames.push(...duplicateNames);
|
|
73124
73164
|
return doc2;
|
|
73125
73165
|
}
|
|
@@ -73154,6 +73194,23 @@ class MarkdownDocument {
|
|
|
73154
73194
|
get duplicateSectionNames() {
|
|
73155
73195
|
return this._duplicateSectionNames;
|
|
73156
73196
|
}
|
|
73197
|
+
unclosedFenceLine() {
|
|
73198
|
+
return this._unclosedFenceLine;
|
|
73199
|
+
}
|
|
73200
|
+
assertFenceBalance(name, body) {
|
|
73201
|
+
const docFenceLine = this._unclosedFenceLine;
|
|
73202
|
+
if (docFenceLine !== null) {
|
|
73203
|
+
throw new Error(`Cannot write section "${name}": the document has an unclosed code fence at line ${docFenceLine}; ` + "later sections are hidden and would be dropped by this write. Close the fence and re-parse.");
|
|
73204
|
+
}
|
|
73205
|
+
if (this._duplicateSectionNames.length > 0) {
|
|
73206
|
+
const names = this._duplicateSectionNames.map((n2) => `"${n2}"`).join(", ");
|
|
73207
|
+
throw new Error(`Cannot write section "${name}": the document has duplicate top-level sections (${names}); ` + "the serialized result would have fewer sections than the input and the duplicates would be dropped. " + "Merge or remove the duplicates and re-parse.");
|
|
73208
|
+
}
|
|
73209
|
+
const bodyFenceLine = findUnclosedFenceLine(body);
|
|
73210
|
+
if (bodyFenceLine !== null) {
|
|
73211
|
+
throw new Error(`Cannot write section "${name}": the new body has an unclosed code fence (opening at body line ${bodyFenceLine}); ` + "writing it would leave the document inside an open fence and hide later sections.");
|
|
73212
|
+
}
|
|
73213
|
+
}
|
|
73157
73214
|
get canonicalSections() {
|
|
73158
73215
|
const base2 = this._domain === "task" ? TASK_CANONICAL_SECTIONS : FEATURE_CANONICAL_SECTIONS;
|
|
73159
73216
|
const extra = UNIVERSAL_SECTIONS.filter((s2) => !base2.includes(s2));
|
|
@@ -73182,6 +73239,7 @@ class MarkdownDocument {
|
|
|
73182
73239
|
}
|
|
73183
73240
|
replaceSection(name, body) {
|
|
73184
73241
|
this.validateSectionName(name);
|
|
73242
|
+
this.assertFenceBalance(name, body);
|
|
73185
73243
|
const cleaned = this.stripSameLevelHeadings(body);
|
|
73186
73244
|
const section = this.findSection(name);
|
|
73187
73245
|
if (section !== undefined) {
|
|
@@ -73245,6 +73303,7 @@ ${body.trimEnd()}
|
|
|
73245
73303
|
}
|
|
73246
73304
|
replaceMarkerRegion(name, content) {
|
|
73247
73305
|
this.validateSectionName(name);
|
|
73306
|
+
this.assertFenceBalance(name, content);
|
|
73248
73307
|
const section = this.findSection(name);
|
|
73249
73308
|
if (section === undefined) {
|
|
73250
73309
|
throw new Error(`Section "${name}" does not exist in this document.`);
|
|
@@ -74133,6 +74192,7 @@ __export(exports_src, {
|
|
|
74133
74192
|
createDefaultRegistry: () => createDefaultRegistry,
|
|
74134
74193
|
createBoundaryContext: () => createBoundaryContext,
|
|
74135
74194
|
countToolCallsSince: () => countToolCallsSince,
|
|
74195
|
+
countToolCallArgsRaw: () => countToolCallArgsRaw,
|
|
74136
74196
|
countCheckpointsBySource: () => countCheckpointsBySource,
|
|
74137
74197
|
consolidatedTimeline: () => consolidatedTimeline,
|
|
74138
74198
|
computeSnapshotHash: () => computeSnapshotHash,
|
|
@@ -78315,7 +78375,8 @@ class AgentService {
|
|
|
78315
78375
|
cacheInfo = { hit: true, ageMs: serveCached.ageMs, path: DOCTOR_CACHE_REL };
|
|
78316
78376
|
return this.renderDoctor(serveCached.results, executors, args.json, undefined, cacheInfo, args.enveloped, usage);
|
|
78317
78377
|
}
|
|
78318
|
-
const
|
|
78378
|
+
const allDisabled = (executors?.length ?? 0) > 0 && enabledExecutors?.length === 0;
|
|
78379
|
+
const probeResults = allDisabled ? [] : await doctorRunner.runAll();
|
|
78319
78380
|
const results2 = withDisabledRows(probeResults, executors);
|
|
78320
78381
|
if (cacheOn && !args.probeHealth) {
|
|
78321
78382
|
const writeErr = await writeDoctorCacheFile(fileSystem, fingerprint2, results2, now2);
|
|
@@ -81511,6 +81572,82 @@ var init_finding_codes2 = __esm(() => {
|
|
|
81511
81572
|
init_src();
|
|
81512
81573
|
});
|
|
81513
81574
|
|
|
81575
|
+
// ../../packages/app/src/services/feature-inventory.ts
|
|
81576
|
+
function parseInventory(report) {
|
|
81577
|
+
const lines = report.split(`
|
|
81578
|
+
`);
|
|
81579
|
+
const start = lines.findIndex((line) => /^#{1,6}\s*Requirement inventory\s*$/i.test(line));
|
|
81580
|
+
if (start === -1)
|
|
81581
|
+
return { owing: new Set, deferred: new Set, hasSection: false };
|
|
81582
|
+
const owing = new Set;
|
|
81583
|
+
const deferred = new Set;
|
|
81584
|
+
for (let i2 = start + 1;i2 < lines.length; i2++) {
|
|
81585
|
+
if (/^#{1,6}\s/.test(lines[i2] ?? ""))
|
|
81586
|
+
break;
|
|
81587
|
+
const match = INVENTORY_ITEM_RE.exec(lines[i2] ?? "");
|
|
81588
|
+
if (match === null)
|
|
81589
|
+
continue;
|
|
81590
|
+
const id = `I${match[1]}`;
|
|
81591
|
+
if (/\[\s*deferred\s*:/i.test(match[2] ?? ""))
|
|
81592
|
+
deferred.add(id);
|
|
81593
|
+
else
|
|
81594
|
+
owing.add(id);
|
|
81595
|
+
}
|
|
81596
|
+
return { owing, deferred, hasSection: true };
|
|
81597
|
+
}
|
|
81598
|
+
function parseCoverage(ac) {
|
|
81599
|
+
const covered = new Map;
|
|
81600
|
+
let inScenario = false;
|
|
81601
|
+
for (const line of ac.split(`
|
|
81602
|
+
`)) {
|
|
81603
|
+
if (SCENARIO_RE.test(line)) {
|
|
81604
|
+
inScenario = true;
|
|
81605
|
+
continue;
|
|
81606
|
+
}
|
|
81607
|
+
if (!inScenario)
|
|
81608
|
+
continue;
|
|
81609
|
+
const match = COVERS_RE.exec(line);
|
|
81610
|
+
if (match === null)
|
|
81611
|
+
continue;
|
|
81612
|
+
for (const raw of (match[1] ?? "").split(",")) {
|
|
81613
|
+
const id = raw.trim().toUpperCase();
|
|
81614
|
+
if (/^I\d+$/.test(id))
|
|
81615
|
+
covered.set(id, (covered.get(id) ?? 0) + 1);
|
|
81616
|
+
}
|
|
81617
|
+
}
|
|
81618
|
+
return covered;
|
|
81619
|
+
}
|
|
81620
|
+
function checkInventoryCoverage(reportText, acText) {
|
|
81621
|
+
const inventory = parseInventory(reportText);
|
|
81622
|
+
if (!inventory.hasSection || inventory.owing.size + inventory.deferred.size === 0) {
|
|
81623
|
+
return [
|
|
81624
|
+
{
|
|
81625
|
+
layer: "L3",
|
|
81626
|
+
code: FINDING_CODES.INVENTORY_COVERAGE,
|
|
81627
|
+
severity: "error",
|
|
81628
|
+
section: "Requirements",
|
|
81629
|
+
message: "inventory report has no `## Requirement inventory` items"
|
|
81630
|
+
}
|
|
81631
|
+
];
|
|
81632
|
+
}
|
|
81633
|
+
const covered = parseCoverage(acText);
|
|
81634
|
+
const uncovered = [...inventory.owing].filter((id) => (covered.get(id) ?? 0) === 0).sort();
|
|
81635
|
+
return uncovered.map((id) => ({
|
|
81636
|
+
layer: "L3",
|
|
81637
|
+
code: FINDING_CODES.INVENTORY_COVERAGE,
|
|
81638
|
+
severity: "error",
|
|
81639
|
+
section: "Requirements",
|
|
81640
|
+
message: `Requirement ${id} is not covered by any AC scenario and is not [deferred:]`
|
|
81641
|
+
}));
|
|
81642
|
+
}
|
|
81643
|
+
var INVENTORY_ITEM_RE, COVERS_RE, SCENARIO_RE;
|
|
81644
|
+
var init_feature_inventory = __esm(() => {
|
|
81645
|
+
init_finding_codes2();
|
|
81646
|
+
INVENTORY_ITEM_RE = /^\s*[-*]\s+\**I(\d+)\**\s*[.:\u2014-]?\s+(.*)$/;
|
|
81647
|
+
COVERS_RE = /^\s*#\s*covers:\s*(.+)$/i;
|
|
81648
|
+
SCENARIO_RE = /^\s*Scenario(?:\s+Outline)?:/;
|
|
81649
|
+
});
|
|
81650
|
+
|
|
81514
81651
|
// ../../packages/app/src/services/structural-repair.ts
|
|
81515
81652
|
function canonicalOrder(domain2) {
|
|
81516
81653
|
return domain2 === "task" ? TASK_CANONICAL_SECTIONS : FEATURE_CANONICAL_SECTIONS;
|
|
@@ -81834,6 +81971,16 @@ class PlanningCheckService {
|
|
|
81834
81971
|
return doc2;
|
|
81835
81972
|
}
|
|
81836
81973
|
runL2(doc2, entry, findings, raw) {
|
|
81974
|
+
const fenceLine = doc2.unclosedFenceLine();
|
|
81975
|
+
if (fenceLine !== null) {
|
|
81976
|
+
findings.push({
|
|
81977
|
+
layer: "L2",
|
|
81978
|
+
code: FINDING_CODES.L2_UNCLOSED_CODE_FENCE,
|
|
81979
|
+
severity: "error",
|
|
81980
|
+
section: "",
|
|
81981
|
+
message: `Unclosed code fence at line ${fenceLine}: all later sections are hidden from this check. Close the fence to restore them.`
|
|
81982
|
+
});
|
|
81983
|
+
}
|
|
81837
81984
|
if (!entry)
|
|
81838
81985
|
return;
|
|
81839
81986
|
const sectionNames = doc2.sectionNames;
|
|
@@ -81984,6 +82131,7 @@ var init_planning_check_base = __esm(() => {
|
|
|
81984
82131
|
FINDING_CODES.L1_MARKDOWN_PARSE,
|
|
81985
82132
|
FINDING_CODES.L1_SCHEMA_VALIDATION,
|
|
81986
82133
|
FINDING_CODES.L2_MISSING_REQUIRED_SECTION,
|
|
82134
|
+
FINDING_CODES.L2_UNCLOSED_CODE_FENCE,
|
|
81987
82135
|
FINDING_CODES.L3_AC_BDD_ERROR,
|
|
81988
82136
|
FINDING_CODES.L3_AC_BDD_INVALID,
|
|
81989
82137
|
FINDING_CODES.L3_REQUIREMENTS_EMPTY,
|
|
@@ -81991,7 +82139,10 @@ var init_planning_check_base = __esm(() => {
|
|
|
81991
82139
|
FINDING_CODES.L3_REQUIRED_SECTION_PLACEHOLDER,
|
|
81992
82140
|
...REQUIRED_REFERENCE_CODES,
|
|
81993
82141
|
FINDING_CODES.L4_PREREQUISITE_CYCLE,
|
|
81994
|
-
...COMPLETION_FINDING_CODES
|
|
82142
|
+
...COMPLETION_FINDING_CODES,
|
|
82143
|
+
FINDING_CODES.PRECHECK_SIZE,
|
|
82144
|
+
FINDING_CODES.PRECHECK_EVIDENCE,
|
|
82145
|
+
FINDING_CODES.INVENTORY_COVERAGE
|
|
81995
82146
|
]);
|
|
81996
82147
|
});
|
|
81997
82148
|
|
|
@@ -82049,20 +82200,21 @@ function prefixId(id) {
|
|
|
82049
82200
|
const m = /^(?:AC|R)\d+/.exec(id);
|
|
82050
82201
|
return m ? m[0] : id;
|
|
82051
82202
|
}
|
|
82052
|
-
function acRowProves(rowId, items) {
|
|
82053
|
-
const
|
|
82203
|
+
function acRowProves(rowId, items, scenarioTitles) {
|
|
82204
|
+
const ordinal = /^AC-(\d+)$/.exec(rowId.trim());
|
|
82205
|
+
const key2 = normalizeKey2(ordinal ? scenarioTitles[Number(ordinal[1]) - 1] ?? "" : rowId);
|
|
82054
82206
|
for (const item of items) {
|
|
82055
82207
|
if (!item.requirementId?.startsWith("AC"))
|
|
82056
82208
|
continue;
|
|
82057
82209
|
const alias3 = normalizeKey2(item.text.replace(/\s*\(req:[^)]*\)\s*$/, ""));
|
|
82058
|
-
if (alias3 !== "" && (alias3 === key2 || alias3.endsWith(`\u2014 ${key2}`)))
|
|
82210
|
+
if (alias3 !== "" && key2 !== "" && (alias3 === key2 || alias3.endsWith(`\u2014 ${key2}`)))
|
|
82059
82211
|
return item.requirementId;
|
|
82060
82212
|
}
|
|
82061
|
-
if (/^R\d+\s*[\u2014-]\s*\S/.test(rowId.trim()))
|
|
82213
|
+
if (ordinal || /^R\d+\s*[\u2014-]\s*\S/.test(rowId.trim()))
|
|
82062
82214
|
return;
|
|
82063
82215
|
return prefixId(rowId);
|
|
82064
82216
|
}
|
|
82065
|
-
function flipVerifiedCheckboxes(body, verdict) {
|
|
82217
|
+
function flipVerifiedCheckboxes(body, verdict, scenarioTitles = []) {
|
|
82066
82218
|
if (verdict.verdict === "FAIL" || verdict.verdict === "UNKNOWN")
|
|
82067
82219
|
return body;
|
|
82068
82220
|
const items = parseChecklist(body);
|
|
@@ -82070,13 +82222,16 @@ function flipVerifiedCheckboxes(body, verdict) {
|
|
|
82070
82222
|
return body;
|
|
82071
82223
|
const proven = new Set;
|
|
82072
82224
|
for (const req of verdict.requirements) {
|
|
82073
|
-
if (req.status
|
|
82074
|
-
|
|
82225
|
+
if (req.status !== "MET")
|
|
82226
|
+
continue;
|
|
82227
|
+
const id = /^AC-\d+$/.test(req.id.trim()) ? acRowProves(req.id, items, scenarioTitles) : prefixId(req.id);
|
|
82228
|
+
if (id !== undefined)
|
|
82229
|
+
proven.add(id);
|
|
82075
82230
|
}
|
|
82076
82231
|
for (const ac of verdict.acceptanceCriteria ?? []) {
|
|
82077
82232
|
if (ac.status !== "MET")
|
|
82078
82233
|
continue;
|
|
82079
|
-
const id = acRowProves(ac.id, items);
|
|
82234
|
+
const id = acRowProves(ac.id, items, scenarioTitles);
|
|
82080
82235
|
if (id !== undefined)
|
|
82081
82236
|
proven.add(id);
|
|
82082
82237
|
}
|
|
@@ -82359,6 +82514,288 @@ var init_task_record = __esm(() => {
|
|
|
82359
82514
|
};
|
|
82360
82515
|
});
|
|
82361
82516
|
|
|
82517
|
+
// ../../packages/app/src/services/verify-answer-lint.ts
|
|
82518
|
+
function lintVerifyAnswer(answerText, taskContent, featureContent) {
|
|
82519
|
+
const findings = [];
|
|
82520
|
+
const add = (line, rule, message) => {
|
|
82521
|
+
if (findings.length < ANSWER_LINT_MAX_FINDINGS)
|
|
82522
|
+
findings.push({ line, rule, message });
|
|
82523
|
+
};
|
|
82524
|
+
const tables = parseAnswer(answerText);
|
|
82525
|
+
if (tables.verdict === null) {
|
|
82526
|
+
add(0, "verdict-missing", "no `Verdict:` line (expected exactly one `Verdict: PASS|PARTIAL|FAIL` line)");
|
|
82527
|
+
} else if (!/^(PASS|PARTIAL|FAIL)$/i.test(tables.verdict.value)) {
|
|
82528
|
+
add(tables.verdict.line, "verdict-value", `invalid Verdict value "${tables.verdict.value}" (PASS | PARTIAL | FAIL)`);
|
|
82529
|
+
}
|
|
82530
|
+
const reqIds = extractRequirementIds(taskContent);
|
|
82531
|
+
const acIndex = buildAcIdentityIndex(taskContent, featureContent ?? null);
|
|
82532
|
+
const seenReq = new Set;
|
|
82533
|
+
for (const row of tables.reqs) {
|
|
82534
|
+
if (!reqIds.includes(row.id))
|
|
82535
|
+
add(row.line, "req-unknown", `unknown requirement ID "${row.id}" (task declares: ${reqIds.join(", ") || "none"})`);
|
|
82536
|
+
else if (seenReq.has(row.id))
|
|
82537
|
+
add(row.line, "req-duplicate", `duplicate requirement row "${row.id}"`);
|
|
82538
|
+
seenReq.add(row.id);
|
|
82539
|
+
if (normalizeReqStatus(row.status) === null)
|
|
82540
|
+
add(row.line, "req-status", `"${row.id}" invalid status "${row.status}" (MET | PARTIAL | UNMET)`);
|
|
82541
|
+
if (!row.evidence.trim())
|
|
82542
|
+
add(row.line, "req-evidence", `"${row.id}" has empty evidence`);
|
|
82543
|
+
}
|
|
82544
|
+
for (const id of reqIds) {
|
|
82545
|
+
if (!seenReq.has(id))
|
|
82546
|
+
add(0, "req-missing", `missing requirement row for "${id}"`);
|
|
82547
|
+
}
|
|
82548
|
+
const seenAc = new Map;
|
|
82549
|
+
for (const row of tables.acs) {
|
|
82550
|
+
const resolution = resolveAcIdentity(row.id, acIndex);
|
|
82551
|
+
const canonicalKey = resolution.ok ? normalizeAcTitle(resolution.canonical) : null;
|
|
82552
|
+
if (!resolution.ok) {
|
|
82553
|
+
if (resolution.error !== "")
|
|
82554
|
+
add(row.line, "ac-ordinal", resolution.error);
|
|
82555
|
+
else
|
|
82556
|
+
add(row.line, "ac-identity", `AC ID "${row.id.slice(0, 60)}" matches no task AC checklist label or scenario title ` + "(accepted forms: exact title, bare title, a criterion bullet's bold head or full bold span, " + "`Scenario:` prefix, bracket tags, declared AC-N alias)");
|
|
82557
|
+
} else if (canonicalKey !== null && seenAc.has(canonicalKey)) {
|
|
82558
|
+
const first = seenAc.get(canonicalKey) ?? "";
|
|
82559
|
+
add(row.line, "ac-duplicate", `duplicate AC row "${row.id.slice(0, 60)}" \u2014 alias-equivalent to "${first.slice(0, 60)}"`);
|
|
82560
|
+
}
|
|
82561
|
+
if (canonicalKey !== null && !seenAc.has(canonicalKey))
|
|
82562
|
+
seenAc.set(canonicalKey, row.id);
|
|
82563
|
+
if (normalizeAcStatus(row.status) === null)
|
|
82564
|
+
add(row.line, "ac-status", `invalid AC status "${row.status}" (MET | PARTIAL | UNMET | N/A)`);
|
|
82565
|
+
if (normalizeEvidenceType(row.evidenceType) === null)
|
|
82566
|
+
add(row.line, "ac-evidence-type", `invalid evidence type "${row.evidenceType}" (test | command | static-ref | manual-review | llm-judge | n/a, or a + compound)`);
|
|
82567
|
+
if (!row.evidence.trim())
|
|
82568
|
+
add(row.line, "ac-evidence", `AC "${row.id.slice(0, 40)}" has empty evidence`);
|
|
82569
|
+
}
|
|
82570
|
+
return findings;
|
|
82571
|
+
}
|
|
82572
|
+
function splitTableCells(line) {
|
|
82573
|
+
return line.split(/(?<!\\)\|/).map((c3) => c3.replace(/\\\|/g, "|").trim()).filter(Boolean);
|
|
82574
|
+
}
|
|
82575
|
+
function normalizeReqStatus(raw) {
|
|
82576
|
+
if (/\bMET\b/.test(raw))
|
|
82577
|
+
return "MET";
|
|
82578
|
+
if (/\bPARTIAL\b/.test(raw))
|
|
82579
|
+
return "PARTIAL";
|
|
82580
|
+
if (/\bUNMET\b/.test(raw))
|
|
82581
|
+
return "UNMET";
|
|
82582
|
+
return null;
|
|
82583
|
+
}
|
|
82584
|
+
function normalizeAcStatus(raw) {
|
|
82585
|
+
if (/\bMET\b/.test(raw))
|
|
82586
|
+
return "MET";
|
|
82587
|
+
if (/\bPARTIAL\b/.test(raw))
|
|
82588
|
+
return "PARTIAL";
|
|
82589
|
+
if (/\bUNMET\b/.test(raw))
|
|
82590
|
+
return "UNMET";
|
|
82591
|
+
if (/\bN\/A\b/.test(raw) || /\bNA\b/.test(raw))
|
|
82592
|
+
return "N/A";
|
|
82593
|
+
return null;
|
|
82594
|
+
}
|
|
82595
|
+
function normalizeEvidenceTypeToken(normalized) {
|
|
82596
|
+
if (normalized === "test")
|
|
82597
|
+
return "test";
|
|
82598
|
+
if (normalized === "command")
|
|
82599
|
+
return "command";
|
|
82600
|
+
if (normalized === "static-ref" || normalized === "static" || normalized === "doc" || normalized === "docs" || normalized === "documentation") {
|
|
82601
|
+
return "static-ref";
|
|
82602
|
+
}
|
|
82603
|
+
if (normalized === "manual-review" || normalized === "manual")
|
|
82604
|
+
return "manual-review";
|
|
82605
|
+
if (normalized === "llm-judge" || normalized === "judge")
|
|
82606
|
+
return "llm-judge";
|
|
82607
|
+
if (normalized === "n/a" || normalized === "na")
|
|
82608
|
+
return "n/a";
|
|
82609
|
+
return null;
|
|
82610
|
+
}
|
|
82611
|
+
function normalizeEvidenceType(raw) {
|
|
82612
|
+
const normalized = raw.toLowerCase().trim();
|
|
82613
|
+
const single = normalizeEvidenceTypeToken(normalized);
|
|
82614
|
+
if (single !== null)
|
|
82615
|
+
return single;
|
|
82616
|
+
const parts = normalized.split(/[+,/]/).filter((p) => p.trim());
|
|
82617
|
+
const tokens = parts.map((part) => normalizeEvidenceTypeToken(part.trim())).filter((t) => t !== null);
|
|
82618
|
+
if (tokens.length < 2 || tokens.length !== parts.length)
|
|
82619
|
+
return null;
|
|
82620
|
+
return EVIDENCE_TYPE_PRECEDENCE.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
82621
|
+
}
|
|
82622
|
+
function parseAnswer(text4) {
|
|
82623
|
+
const out = { verdict: null, reqs: [], acs: [] };
|
|
82624
|
+
const lines = text4.split(`
|
|
82625
|
+
`);
|
|
82626
|
+
let reqTable = false;
|
|
82627
|
+
let acTable = false;
|
|
82628
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
82629
|
+
const trimmed = lines[i2]?.trim() ?? "";
|
|
82630
|
+
const lineNo = i2 + 1;
|
|
82631
|
+
if ((reqTable || acTable) && /^#{1,6}\s/.test(trimmed)) {
|
|
82632
|
+
reqTable = false;
|
|
82633
|
+
acTable = false;
|
|
82634
|
+
continue;
|
|
82635
|
+
}
|
|
82636
|
+
if (!trimmed.startsWith("|"))
|
|
82637
|
+
continue;
|
|
82638
|
+
const cells = splitTableCells(trimmed);
|
|
82639
|
+
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
82640
|
+
continue;
|
|
82641
|
+
const h0 = (cells[0] ?? "").toLowerCase();
|
|
82642
|
+
const h1 = (cells[1] ?? "").toLowerCase();
|
|
82643
|
+
if (!reqTable && !acTable && cells.length >= 2) {
|
|
82644
|
+
const idLike = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
82645
|
+
if (idLike && (h1.includes("status") || h1 === "verdict")) {
|
|
82646
|
+
reqTable = true;
|
|
82647
|
+
continue;
|
|
82648
|
+
}
|
|
82649
|
+
if ((h0 === "ac" || h0.includes("acceptance")) && h1.includes("status") && (cells[2] ?? "").toLowerCase().includes("evidence")) {
|
|
82650
|
+
acTable = true;
|
|
82651
|
+
continue;
|
|
82652
|
+
}
|
|
82653
|
+
}
|
|
82654
|
+
if (reqTable && cells.length >= 2) {
|
|
82655
|
+
if ((h0 === "ac" || h0.includes("acceptance")) && h1.includes("status")) {
|
|
82656
|
+
reqTable = false;
|
|
82657
|
+
acTable = true;
|
|
82658
|
+
continue;
|
|
82659
|
+
}
|
|
82660
|
+
out.reqs.push({
|
|
82661
|
+
id: cells[0] ?? "",
|
|
82662
|
+
status: (cells[1] ?? "").toUpperCase(),
|
|
82663
|
+
evidence: cells[2] ?? "",
|
|
82664
|
+
line: lineNo
|
|
82665
|
+
});
|
|
82666
|
+
continue;
|
|
82667
|
+
}
|
|
82668
|
+
if (acTable && cells.length >= 3) {
|
|
82669
|
+
out.acs.push({
|
|
82670
|
+
id: cells[0] ?? "",
|
|
82671
|
+
status: cells[1] ?? "",
|
|
82672
|
+
evidenceType: cells[2] ?? "",
|
|
82673
|
+
evidence: cells[3] ?? "",
|
|
82674
|
+
line: lineNo
|
|
82675
|
+
});
|
|
82676
|
+
}
|
|
82677
|
+
}
|
|
82678
|
+
const verdictMatches = [...text4.matchAll(/^\s*Verdict:\s*(\S+)\s*$/gim)];
|
|
82679
|
+
if (verdictMatches.length === 1) {
|
|
82680
|
+
out.verdict = {
|
|
82681
|
+
value: verdictMatches[0]?.[1] ?? "",
|
|
82682
|
+
line: text4.slice(0, verdictMatches[0]?.index ?? 0).split(`
|
|
82683
|
+
`).length
|
|
82684
|
+
};
|
|
82685
|
+
}
|
|
82686
|
+
return out;
|
|
82687
|
+
}
|
|
82688
|
+
function sectionBetween(text4, heading) {
|
|
82689
|
+
const marker = new RegExp(`^#{1,6}\\s+${heading}\\s*$`, "im");
|
|
82690
|
+
const match = marker.exec(text4);
|
|
82691
|
+
if (!match || match.index === undefined)
|
|
82692
|
+
return "";
|
|
82693
|
+
const rest = text4.slice(match.index);
|
|
82694
|
+
const lineEnd = rest.indexOf(`
|
|
82695
|
+
`);
|
|
82696
|
+
const afterHeading = lineEnd === -1 ? "" : rest.slice(lineEnd + 1);
|
|
82697
|
+
const next = afterHeading.search(/^#{1,6}\s/m);
|
|
82698
|
+
return next === -1 ? afterHeading : afterHeading.slice(0, next);
|
|
82699
|
+
}
|
|
82700
|
+
function extractRequirementIds(taskContent) {
|
|
82701
|
+
const section = sectionBetween(taskContent, "Requirements");
|
|
82702
|
+
const ids = new Set;
|
|
82703
|
+
for (const m of section.matchAll(/\*\*(R\d+(?:\.\d+)*)\b/g))
|
|
82704
|
+
ids.add(m[1] ?? "");
|
|
82705
|
+
for (const m of section.matchAll(/^(?:[-*]\s+(?:\[[ xX]\]\s+)?|\[[ xX]\]\s+)(R\d+(?:\.\d+)*)/gm))
|
|
82706
|
+
ids.add(m[1] ?? "");
|
|
82707
|
+
for (const m of section.matchAll(/^(R\d+(?:\.\d+)*)\s*[.:]/gm))
|
|
82708
|
+
ids.add(m[1] ?? "");
|
|
82709
|
+
return [...ids];
|
|
82710
|
+
}
|
|
82711
|
+
function stripAcWrappers(title) {
|
|
82712
|
+
let out = title.trim();
|
|
82713
|
+
let prev;
|
|
82714
|
+
do {
|
|
82715
|
+
prev = out;
|
|
82716
|
+
out = out.replace(/^\[[^\]]*\]\s*/, "").replace(/\s*\[[^\]]*\]\s*$/, "").replace(/^Scenario:\s*/i, "").trim();
|
|
82717
|
+
} while (out !== prev);
|
|
82718
|
+
return out;
|
|
82719
|
+
}
|
|
82720
|
+
function normalizeAcTitle(title) {
|
|
82721
|
+
return stripAcWrappers(title).replace(/^R\d+\s*[:\-\u2014]?\s*/, "").toLowerCase().replace(/[\u0027\u2018\u2019\u201c\u201d]/g, "").replace(/\s+/g, " ").replace(/\s*\.+$/, "").trim();
|
|
82722
|
+
}
|
|
82723
|
+
function buildAcIdentityIndex(taskContent, featureContent) {
|
|
82724
|
+
const byTitle = new Map;
|
|
82725
|
+
const declareIdentity = (spelling) => {
|
|
82726
|
+
const key2 = normalizeAcTitle(spelling);
|
|
82727
|
+
if (key2 !== "" && !byTitle.has(key2))
|
|
82728
|
+
byTitle.set(key2, spelling);
|
|
82729
|
+
};
|
|
82730
|
+
const section = sectionBetween(taskContent, "Acceptance Criteria");
|
|
82731
|
+
for (const m of section.matchAll(/^[-*]\s+(?:\[[ xX]\]\s+)?(.+?)\s*(?::|$)/gm)) {
|
|
82732
|
+
const label = (m[1] ?? "").trim();
|
|
82733
|
+
if (!label)
|
|
82734
|
+
continue;
|
|
82735
|
+
declareIdentity(label);
|
|
82736
|
+
const leading = label.split(/\s+/)[0] ?? "";
|
|
82737
|
+
if (leading && leading !== label)
|
|
82738
|
+
declareIdentity(leading);
|
|
82739
|
+
}
|
|
82740
|
+
for (const m of section.matchAll(/^[-*]\s+(?:\[[ xX]\]\s+)?\*\*(.+?)\*\*/gm)) {
|
|
82741
|
+
const inner = (m[1] ?? "").trim();
|
|
82742
|
+
if (!inner)
|
|
82743
|
+
continue;
|
|
82744
|
+
declareIdentity(inner);
|
|
82745
|
+
const head = inner.split(/\s+[\u2014\u2013]\s+|:/)[0]?.trim() ?? "";
|
|
82746
|
+
if (head && head !== inner)
|
|
82747
|
+
declareIdentity(head);
|
|
82748
|
+
}
|
|
82749
|
+
for (const m of section.matchAll(/^\*\*(.+?)\*\*\s*$/gm)) {
|
|
82750
|
+
const inner = (m[1] ?? "").trim();
|
|
82751
|
+
if (!inner)
|
|
82752
|
+
continue;
|
|
82753
|
+
declareIdentity(inner);
|
|
82754
|
+
const head = inner.split(":")[0]?.trim() ?? "";
|
|
82755
|
+
if (head && head !== inner)
|
|
82756
|
+
declareIdentity(head);
|
|
82757
|
+
}
|
|
82758
|
+
const scenarioTitles = (content) => [...content.matchAll(/^[ \t]*Scenario:\s*(.+)\s*$/gm)].map((m) => (m[1] ?? "").trim()).filter((t) => t !== "");
|
|
82759
|
+
const taskScenarios = scenarioTitles(sectionBetween(taskContent, "Acceptance Criteria"));
|
|
82760
|
+
const featureScenarios = featureContent !== null ? scenarioTitles(featureContent) : [];
|
|
82761
|
+
for (const title of [...taskScenarios, ...featureScenarios])
|
|
82762
|
+
declareIdentity(title);
|
|
82763
|
+
return { byTitle, taskScenarios, featureScenarios };
|
|
82764
|
+
}
|
|
82765
|
+
function resolveAcIdentity(rowId, index2) {
|
|
82766
|
+
const canonical = index2.byTitle.get(normalizeAcTitle(rowId));
|
|
82767
|
+
if (canonical !== undefined)
|
|
82768
|
+
return { ok: true, canonical };
|
|
82769
|
+
const stripped = stripAcWrappers(rowId);
|
|
82770
|
+
const ordinal = /^AC-(\d+)$/i.exec(stripped);
|
|
82771
|
+
if (ordinal !== null) {
|
|
82772
|
+
const n2 = Number(ordinal[1]);
|
|
82773
|
+
const taskTitle = index2.taskScenarios[n2 - 1];
|
|
82774
|
+
const featureTitle = index2.featureScenarios[n2 - 1];
|
|
82775
|
+
const candidates = [...new Set([taskTitle, featureTitle].filter((t) => t !== undefined))];
|
|
82776
|
+
if (candidates.length === 0) {
|
|
82777
|
+
return {
|
|
82778
|
+
ok: false,
|
|
82779
|
+
error: `AC id "${rowId}" uses the AC-${n2} positional alias but no scenario exists at that ordinal ` + "(task scenario list and linked-feature scenario list) \u2014 cite the exact scenario title, checklist label, or a bare `**AC id**` paragraph"
|
|
82780
|
+
};
|
|
82781
|
+
}
|
|
82782
|
+
if (candidates.length > 1) {
|
|
82783
|
+
return {
|
|
82784
|
+
ok: false,
|
|
82785
|
+
error: `AC id "${rowId}" is ambiguous: task AC #${n2} ("${taskTitle}") and feature scenario #${n2} ` + `("${featureTitle}") are different scenarios with different ordering \u2014 cite the exact title`
|
|
82786
|
+
};
|
|
82787
|
+
}
|
|
82788
|
+
const resolved = index2.byTitle.get(normalizeAcTitle(candidates[0] ?? ""));
|
|
82789
|
+
if (resolved !== undefined)
|
|
82790
|
+
return { ok: true, canonical: resolved };
|
|
82791
|
+
}
|
|
82792
|
+
return { ok: false, error: "" };
|
|
82793
|
+
}
|
|
82794
|
+
var ANSWER_LINT_MAX_FINDINGS = 10, EVIDENCE_TYPE_PRECEDENCE;
|
|
82795
|
+
var init_verify_answer_lint = __esm(() => {
|
|
82796
|
+
EVIDENCE_TYPE_PRECEDENCE = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
82797
|
+
});
|
|
82798
|
+
|
|
82362
82799
|
// ../../packages/app/src/services/feature-check.ts
|
|
82363
82800
|
var exports_feature_check = {};
|
|
82364
82801
|
__export(exports_feature_check, {
|
|
@@ -82366,9 +82803,13 @@ __export(exports_feature_check, {
|
|
|
82366
82803
|
verdictRowsMatchScenarios: () => verdictRowsMatchScenarios,
|
|
82367
82804
|
taskCoversAnyFeatureScenario: () => taskCoversAnyFeatureScenario,
|
|
82368
82805
|
summarizeRowIds: () => summarizeRowIds,
|
|
82806
|
+
readFeatureBody: () => readFeatureBody,
|
|
82369
82807
|
readFeatureAcBody: () => readFeatureAcBody,
|
|
82370
82808
|
matchedScenarioKeys: () => matchedScenarioKeys,
|
|
82809
|
+
lintVerifyAnswerForTask: () => lintVerifyAnswerForTask,
|
|
82810
|
+
isFeatureFile: () => isFeatureFile,
|
|
82371
82811
|
findOtherP0InStatus: () => findOtherP0InStatus,
|
|
82812
|
+
featureScenarioTitles: () => featureScenarioTitles,
|
|
82372
82813
|
defaultVerdictRunDir: () => defaultVerdictRunDir,
|
|
82373
82814
|
VERDICT_SCENARIO_KEY_FORMS: () => VERDICT_SCENARIO_KEY_FORMS,
|
|
82374
82815
|
FeatureCheckService: () => FeatureCheckService,
|
|
@@ -82524,6 +82965,9 @@ function matchedScenarioKeys(rows, ac) {
|
|
|
82524
82965
|
const met = rows.filter((r) => r.status === "MET");
|
|
82525
82966
|
return scenarios.filter((sc) => met.some((r) => rowMatchesScenario(r.id, sc))).map((sc) => sc.title);
|
|
82526
82967
|
}
|
|
82968
|
+
function featureScenarioTitles(ac) {
|
|
82969
|
+
return indexScenarioAliases(ac).map((sc) => sc.title);
|
|
82970
|
+
}
|
|
82527
82971
|
function indexScenarioAliases(ac) {
|
|
82528
82972
|
const keys = [];
|
|
82529
82973
|
let inDocString = false;
|
|
@@ -82601,28 +83045,63 @@ function taskCoversAnyFeatureScenario(taskAc, featureAc) {
|
|
|
82601
83045
|
return false;
|
|
82602
83046
|
return scenarioKeys(parsed.scenarios).some((sc) => taskCoversScenario(taskAc, sc));
|
|
82603
83047
|
}
|
|
82604
|
-
|
|
83048
|
+
function isFeatureFile(name, id) {
|
|
83049
|
+
return name.startsWith(`${id}_`) && name.endsWith(".md") && name.length > id.length + 4;
|
|
83050
|
+
}
|
|
83051
|
+
async function readFeatureBody(fs3, featuresDir, featureId2) {
|
|
82605
83052
|
try {
|
|
82606
83053
|
for (const name of await fs3.readDir(featuresDir)) {
|
|
82607
|
-
if (!name
|
|
83054
|
+
if (!isFeatureFile(name, featureId2))
|
|
82608
83055
|
continue;
|
|
82609
|
-
|
|
82610
|
-
const ac = stripAcFence(doc2.getSection("Acceptance Criteria") ?? "");
|
|
82611
|
-
return ac.trim().length > 0 ? ac : null;
|
|
83056
|
+
return await fs3.readFile(`${featuresDir}/${name}`);
|
|
82612
83057
|
}
|
|
82613
83058
|
} catch {}
|
|
82614
83059
|
return null;
|
|
82615
83060
|
}
|
|
83061
|
+
async function readFeatureAcBody(fs3, featuresDir, featureId2) {
|
|
83062
|
+
const raw = await readFeatureBody(fs3, featuresDir, featureId2);
|
|
83063
|
+
if (raw === null)
|
|
83064
|
+
return null;
|
|
83065
|
+
try {
|
|
83066
|
+
const doc2 = MarkdownDocument.parse(raw, "feature");
|
|
83067
|
+
const ac = stripAcFence(doc2.getSection("Acceptance Criteria") ?? "");
|
|
83068
|
+
return ac.trim().length > 0 ? ac : null;
|
|
83069
|
+
} catch {
|
|
83070
|
+
return null;
|
|
83071
|
+
}
|
|
83072
|
+
}
|
|
83073
|
+
async function lintVerifyAnswerForTask(wbs, answerText, deps) {
|
|
83074
|
+
let taskContent;
|
|
83075
|
+
let featureId2 = "";
|
|
83076
|
+
try {
|
|
83077
|
+
const hit = await new TaskLocator({
|
|
83078
|
+
fs: deps.fs,
|
|
83079
|
+
tasksDir: deps.tasksDir,
|
|
83080
|
+
...deps.foldersConfig !== undefined ? { foldersConfig: deps.foldersConfig } : {}
|
|
83081
|
+
}).findByWbs(wbs);
|
|
83082
|
+
if (hit === null)
|
|
83083
|
+
return [];
|
|
83084
|
+
taskContent = await deps.fs.readFile(hit.filePath);
|
|
83085
|
+
const tfm = MarkdownDocument.parse(taskContent, "task").frontmatterData ?? {};
|
|
83086
|
+
featureId2 = tfm.feature_id ?? tfm["feature-id"] ?? "";
|
|
83087
|
+
} catch {
|
|
83088
|
+
return [];
|
|
83089
|
+
}
|
|
83090
|
+
const featureContent = featureId2 !== "" && deps.featuresDir !== undefined ? await readFeatureBody(deps.fs, deps.featuresDir, featureId2) : null;
|
|
83091
|
+
return lintVerifyAnswer(answerText, taskContent, featureContent);
|
|
83092
|
+
}
|
|
82616
83093
|
var DEFAULT_FEATURE_MATRIX, FeatureCheckService, EMBEDDED_SCENARIO_REF_RES, VERDICT_SCENARIO_KEY_FORMS = "scenario title, AC-N alias, (feature R<n>), (covers: R<n>), or [R<n>]";
|
|
82617
83094
|
var init_feature_check = __esm(() => {
|
|
82618
83095
|
init_src2();
|
|
82619
83096
|
init_feature_verification_receipt();
|
|
82620
83097
|
init_workflow_resolver();
|
|
82621
83098
|
init_done_transition_guard();
|
|
83099
|
+
init_feature_inventory();
|
|
82622
83100
|
init_planning_check_base();
|
|
82623
83101
|
init_structural_repair();
|
|
82624
83102
|
init_task_locator();
|
|
82625
83103
|
init_task_record();
|
|
83104
|
+
init_verify_answer_lint();
|
|
82626
83105
|
init_verify_verdict();
|
|
82627
83106
|
DEFAULT_FEATURE_MATRIX = {
|
|
82628
83107
|
variants: {
|
|
@@ -82737,6 +83216,10 @@ var init_feature_check = __esm(() => {
|
|
|
82737
83216
|
const runDir = options?.runDir ?? (primaryTasksDir ? defaultVerdictRunDir(primaryTasksDir) : undefined);
|
|
82738
83217
|
const taskScanDirs = options?.tasksDirs && options.tasksDirs.length > 0 ? options.tasksDirs : options?.tasksDir ? [options.tasksDir] : [];
|
|
82739
83218
|
await this.runL4(doc2, featureId2, effectiveStatus, taskScanDirs, dogfoodDir, runDir, findings);
|
|
83219
|
+
if (typeof options?.inventory === "string") {
|
|
83220
|
+
const acText = doc2.getSection("Acceptance Criteria") ?? "";
|
|
83221
|
+
findings.push(...checkInventoryCoverage(options.inventory, acText));
|
|
83222
|
+
}
|
|
82740
83223
|
return {
|
|
82741
83224
|
id: featureId2,
|
|
82742
83225
|
...this.summarizeWithStatus(effectiveStatus, findings, strict, options?.severityOverrides),
|
|
@@ -83226,6 +83709,111 @@ var init_feature_check = __esm(() => {
|
|
|
83226
83709
|
EMBEDDED_SCENARIO_REF_RES = [/\((?:feature|covers:)[ \t]*(R\d+)\b[^)]*\)/gi, /\[(R\d+)\]/gi];
|
|
83227
83710
|
});
|
|
83228
83711
|
|
|
83712
|
+
// ../../packages/app/src/services/task-evidence-precheck.ts
|
|
83713
|
+
function parseEvidenceChannels(content) {
|
|
83714
|
+
const channels = [];
|
|
83715
|
+
for (const match of content.matchAll(/evidence-channel:\s*(\S+)/g)) {
|
|
83716
|
+
channels.push(match[1] ?? "");
|
|
83717
|
+
}
|
|
83718
|
+
return channels;
|
|
83719
|
+
}
|
|
83720
|
+
async function evaluateTaskEvidence(content, deps) {
|
|
83721
|
+
const declarations = parseEvidenceChannels(content);
|
|
83722
|
+
const unknown2 = declarations.filter((d) => !ALLOWED_EVIDENCE_CHANNELS.includes(d));
|
|
83723
|
+
if (unknown2.length > 0) {
|
|
83724
|
+
return {
|
|
83725
|
+
ok: false,
|
|
83726
|
+
reasons: [
|
|
83727
|
+
`unknown evidence-channel declaration(s): ${unknown2.join(", ")}`,
|
|
83728
|
+
`allowlisted declaration: evidence-channel: ${ALLOWED_EVIDENCE_CHANNELS[0] ?? ""}`
|
|
83729
|
+
]
|
|
83730
|
+
};
|
|
83731
|
+
}
|
|
83732
|
+
if (declarations.length === 0) {
|
|
83733
|
+
return { ok: true, reasons: [] };
|
|
83734
|
+
}
|
|
83735
|
+
const channel = declarations[0] ?? "";
|
|
83736
|
+
const source = /\[([a-z0-9-]+)\]$/.exec(channel)?.[1] ?? "";
|
|
83737
|
+
let count3;
|
|
83738
|
+
try {
|
|
83739
|
+
count3 = await deps.countArgsRaw(source);
|
|
83740
|
+
} catch {
|
|
83741
|
+
count3 = null;
|
|
83742
|
+
}
|
|
83743
|
+
if (count3 === null) {
|
|
83744
|
+
return {
|
|
83745
|
+
ok: false,
|
|
83746
|
+
reasons: [
|
|
83747
|
+
`local spur database or history_tool_call table missing/unreadable for source '${source}' \u2014 run a real history import first`
|
|
83748
|
+
]
|
|
83749
|
+
};
|
|
83750
|
+
}
|
|
83751
|
+
if (!(count3 > 0)) {
|
|
83752
|
+
return {
|
|
83753
|
+
ok: false,
|
|
83754
|
+
reasons: [
|
|
83755
|
+
`0 live ${source} rows with args_raw \u2014 run a non-dry-run ${source} history import with a safe importer before implementing`
|
|
83756
|
+
]
|
|
83757
|
+
};
|
|
83758
|
+
}
|
|
83759
|
+
return { ok: true, reasons: [] };
|
|
83760
|
+
}
|
|
83761
|
+
var ALLOWED_EVIDENCE_CHANNELS;
|
|
83762
|
+
var init_task_evidence_precheck = __esm(() => {
|
|
83763
|
+
init_src2();
|
|
83764
|
+
ALLOWED_EVIDENCE_CHANNELS = ["history_tool_call.args_raw[pi]"];
|
|
83765
|
+
});
|
|
83766
|
+
|
|
83767
|
+
// ../../packages/app/src/services/task-size-precheck.ts
|
|
83768
|
+
function sectionBody(content, headingPattern) {
|
|
83769
|
+
const heading = content.match(new RegExp(headingPattern, "m"));
|
|
83770
|
+
if (!heading)
|
|
83771
|
+
return null;
|
|
83772
|
+
const rest = content.slice((heading.index ?? 0) + heading[0].length);
|
|
83773
|
+
const nextHeading = rest.match(/^#{2,3}\s+/m);
|
|
83774
|
+
return nextHeading ? rest.slice(0, nextHeading.index ?? 0) : rest;
|
|
83775
|
+
}
|
|
83776
|
+
function countRItems(content) {
|
|
83777
|
+
const body = sectionBody(content, "^#{2,3}\\s+Requirements\\s*$") ?? content;
|
|
83778
|
+
const numbers = new Set;
|
|
83779
|
+
for (const match of body.matchAll(new RegExp(R_ITEM_RE_SOURCE, "gm"))) {
|
|
83780
|
+
numbers.add(match[1] ?? "");
|
|
83781
|
+
}
|
|
83782
|
+
return numbers.size;
|
|
83783
|
+
}
|
|
83784
|
+
function countPlanItems(content) {
|
|
83785
|
+
const body = sectionBody(content, "^#{2,3}\\s+Plan\\s*$");
|
|
83786
|
+
if (body === null)
|
|
83787
|
+
return 0;
|
|
83788
|
+
const numbered = body.match(new RegExp(NUMBERED_PLAN_ITEM_RE_SOURCE, "gm"))?.length ?? 0;
|
|
83789
|
+
const checklist = body.match(new RegExp(CHECKLIST_ITEM_RE_SOURCE, "gm"))?.length ?? 0;
|
|
83790
|
+
return numbered + checklist;
|
|
83791
|
+
}
|
|
83792
|
+
function evaluateTaskSize(content, limits = DEFAULT_TASK_SIZE_LIMITS) {
|
|
83793
|
+
const reqCount = countRItems(content);
|
|
83794
|
+
const planItemCount = countPlanItems(content);
|
|
83795
|
+
const reasons = [];
|
|
83796
|
+
if (reqCount > limits.maxReqs) {
|
|
83797
|
+
reasons.push(`Task has ${reqCount} R-items (max ${limits.maxReqs}). Consider decomposing into smaller tasks.`);
|
|
83798
|
+
}
|
|
83799
|
+
if (planItemCount > limits.maxPlanItems) {
|
|
83800
|
+
reasons.push(`Task has ${planItemCount} Plan items (max ${limits.maxPlanItems}). Consider simplifying the plan.`);
|
|
83801
|
+
}
|
|
83802
|
+
return {
|
|
83803
|
+
reqCount,
|
|
83804
|
+
planItemCount,
|
|
83805
|
+
ok: reasons.length === 0,
|
|
83806
|
+
reasons
|
|
83807
|
+
};
|
|
83808
|
+
}
|
|
83809
|
+
var DEFAULT_TASK_SIZE_LIMITS, R_ITEM_RE_SOURCE = "^\\s*[-*]\\s+(?:\\[[ xX]\\]\\s*)?[*_]{0,2}R(\\d+)\\.?[*_]{0,2}(?:\\s|$)", NUMBERED_PLAN_ITEM_RE_SOURCE = "^\\d+\\.\\s", CHECKLIST_ITEM_RE_SOURCE = "^\\s*[-*]\\s+\\[[ xX]\\]";
|
|
83810
|
+
var init_task_size_precheck = __esm(() => {
|
|
83811
|
+
DEFAULT_TASK_SIZE_LIMITS = {
|
|
83812
|
+
maxReqs: 10,
|
|
83813
|
+
maxPlanItems: 16
|
|
83814
|
+
};
|
|
83815
|
+
});
|
|
83816
|
+
|
|
83229
83817
|
// ../../packages/app/src/services/task-check.ts
|
|
83230
83818
|
import { basename as basename8, dirname as dirname14, join as join22 } from "path";
|
|
83231
83819
|
function isPlaceholderBody(body) {
|
|
@@ -83430,9 +84018,12 @@ function hasSolutionFileLineCitation(body) {
|
|
|
83430
84018
|
var TESTING_VERDICT_STUB_RE, EXTERNAL_EVIDENCE_RE, GATE_LANGUAGE_SECTIONS, TaskCheckService;
|
|
83431
84019
|
var init_task_check = __esm(() => {
|
|
83432
84020
|
init_src2();
|
|
84021
|
+
init_feature_check();
|
|
83433
84022
|
init_planning_check_base();
|
|
83434
84023
|
init_structural_repair();
|
|
84024
|
+
init_task_evidence_precheck();
|
|
83435
84025
|
init_task_locator();
|
|
84026
|
+
init_task_size_precheck();
|
|
83436
84027
|
init_verify_verdict();
|
|
83437
84028
|
TESTING_VERDICT_STUB_RE = /^\|\s*[\u2014\u2013-]+\s*\|\s*[\u2014\u2013-]+\s*\|\s*No requirements recorded; verify verdict\s+\S+\s*\|/m;
|
|
83438
84029
|
EXTERNAL_EVIDENCE_RE = /`([^`\n]+?)`\s+(?:line|lines?)\s+(\d+)(?:-(\d+))?/g;
|
|
@@ -83497,6 +84088,29 @@ var init_task_check = __esm(() => {
|
|
|
83497
84088
|
if (effectiveStatus !== "done" && effectiveStatus !== "cancelled") {
|
|
83498
84089
|
await this.runL4Readiness(doc2, fm, wbs, effectiveStatus, findings, tasksDir);
|
|
83499
84090
|
}
|
|
84091
|
+
if (options?.precheck === true) {
|
|
84092
|
+
const size = evaluateTaskSize(doc2.bodyWithoutFrontmatter);
|
|
84093
|
+
for (const reason of size.reasons) {
|
|
84094
|
+
findings.push({
|
|
84095
|
+
layer: "L4",
|
|
84096
|
+
code: FINDING_CODES.PRECHECK_SIZE,
|
|
84097
|
+
severity: "error",
|
|
84098
|
+
section: "",
|
|
84099
|
+
message: reason
|
|
84100
|
+
});
|
|
84101
|
+
}
|
|
84102
|
+
const deps = { countArgsRaw: options.countArgsRaw ?? (async () => null) };
|
|
84103
|
+
const evidence = await evaluateTaskEvidence(doc2.bodyWithoutFrontmatter, deps);
|
|
84104
|
+
for (const reason of evidence.reasons) {
|
|
84105
|
+
findings.push({
|
|
84106
|
+
layer: "L4",
|
|
84107
|
+
code: FINDING_CODES.PRECHECK_EVIDENCE,
|
|
84108
|
+
severity: "error",
|
|
84109
|
+
section: "",
|
|
84110
|
+
message: reason
|
|
84111
|
+
});
|
|
84112
|
+
}
|
|
84113
|
+
}
|
|
83500
84114
|
return {
|
|
83501
84115
|
wbs,
|
|
83502
84116
|
...this.summarizeWithStatus(effectiveStatus, findings, strict, options?.severityOverrides, options?.accepted, wbs, entry?.required),
|
|
@@ -84192,7 +84806,7 @@ var init_task_check = __esm(() => {
|
|
|
84192
84806
|
try {
|
|
84193
84807
|
const entries = await this.fs.readDir(featuresDir);
|
|
84194
84808
|
for (const name of entries) {
|
|
84195
|
-
if (name
|
|
84809
|
+
if (isFeatureFile(name, id)) {
|
|
84196
84810
|
return `${featuresDir}/${name}`;
|
|
84197
84811
|
}
|
|
84198
84812
|
}
|
|
@@ -87183,7 +87797,60 @@ var init_execution_policy = __esm(() => {
|
|
|
87183
87797
|
init_bounded_child_run();
|
|
87184
87798
|
});
|
|
87185
87799
|
|
|
87800
|
+
// ../../packages/app/src/services/feature-sync-suppression.ts
|
|
87801
|
+
import { createHash as createHash9 } from "crypto";
|
|
87802
|
+
function computeSyncFingerprint(input) {
|
|
87803
|
+
const material = [
|
|
87804
|
+
input.featureContentHash,
|
|
87805
|
+
...[...input.taskStatusVector].sort(),
|
|
87806
|
+
...[...input.verdictMtimeVector].sort()
|
|
87807
|
+
].join(`
|
|
87808
|
+
`);
|
|
87809
|
+
return createHash9("sha256").update(material).digest("hex").slice(0, 32);
|
|
87810
|
+
}
|
|
87811
|
+
function serializeBlockedState(state) {
|
|
87812
|
+
return `${JSON.stringify(state)}
|
|
87813
|
+
`;
|
|
87814
|
+
}
|
|
87815
|
+
function parseBlockedState(raw) {
|
|
87816
|
+
const trimmed = raw.trim();
|
|
87817
|
+
if (trimmed.length === 0)
|
|
87818
|
+
return null;
|
|
87819
|
+
try {
|
|
87820
|
+
const parsed = JSON.parse(trimmed);
|
|
87821
|
+
if (typeof parsed.featureId !== "string" || typeof parsed.inputFingerprint !== "string" || typeof parsed.proposal !== "object" || parsed.proposal === null) {
|
|
87822
|
+
return null;
|
|
87823
|
+
}
|
|
87824
|
+
return parsed;
|
|
87825
|
+
} catch {
|
|
87826
|
+
return null;
|
|
87827
|
+
}
|
|
87828
|
+
}
|
|
87829
|
+
async function readVerdictMtimeVector(fs3, runDir) {
|
|
87830
|
+
const dir = runDir.replace(/\/$/, "");
|
|
87831
|
+
let entries;
|
|
87832
|
+
try {
|
|
87833
|
+
entries = await fs3.readDir(dir);
|
|
87834
|
+
} catch {
|
|
87835
|
+
return [];
|
|
87836
|
+
}
|
|
87837
|
+
const vector2 = [];
|
|
87838
|
+
for (const entry of entries) {
|
|
87839
|
+
if (!entry.endsWith("-verdict.json"))
|
|
87840
|
+
continue;
|
|
87841
|
+
try {
|
|
87842
|
+
const stat2 = await fs3.stat(`${dir}/${entry}`);
|
|
87843
|
+
if (stat2)
|
|
87844
|
+
vector2.push(`${entry.replace("-verdict.json", "")}:${stat2.mtimeMs}`);
|
|
87845
|
+
} catch {}
|
|
87846
|
+
}
|
|
87847
|
+
return vector2.sort();
|
|
87848
|
+
}
|
|
87849
|
+
var blockedStateFile = (featureId2, runDir) => `${runDir.replace(/\/$/, "")}/feature-sync-blocked-${featureId2}.json`;
|
|
87850
|
+
var init_feature_sync_suppression = () => {};
|
|
87851
|
+
|
|
87186
87852
|
// ../../packages/app/src/services/feature-service.ts
|
|
87853
|
+
import { createHash as createHash10 } from "crypto";
|
|
87187
87854
|
function isFeatureActionName(action) {
|
|
87188
87855
|
return FEATURE_ACTION_NAMES.includes(action);
|
|
87189
87856
|
}
|
|
@@ -87327,16 +87994,30 @@ class FeatureService {
|
|
|
87327
87994
|
const tasksByFeature = await this.collectTasksByFeature();
|
|
87328
87995
|
const targets = options?.featureId ? features.filter((f) => f.id === options.featureId) : features;
|
|
87329
87996
|
let tasksUpdated = 0;
|
|
87997
|
+
const skipped = [];
|
|
87330
87998
|
for (const feature of targets) {
|
|
87331
87999
|
const rows = tasksByFeature.get(feature.id) ?? [];
|
|
87332
88000
|
const table3 = renderTasksTable(rows);
|
|
87333
88001
|
const raw = await this.ctx.fs.readFile(feature.filePath);
|
|
87334
88002
|
const doc2 = MarkdownDocument.parse(raw, "feature");
|
|
87335
|
-
if (
|
|
88003
|
+
if (doc2.unclosedFenceLine() !== null) {
|
|
88004
|
+
skipped.push({ id: feature.id, reason: "unclosed-code-fence" });
|
|
87336
88005
|
continue;
|
|
88006
|
+
}
|
|
88007
|
+
if (doc2.duplicateSectionNames.length > 0) {
|
|
88008
|
+
skipped.push({ id: feature.id, reason: "duplicate-sections" });
|
|
88009
|
+
continue;
|
|
88010
|
+
}
|
|
88011
|
+
if (!doc2.hasSection("Tasks")) {
|
|
88012
|
+
skipped.push({ id: feature.id, reason: "missing-tasks-section" });
|
|
88013
|
+
continue;
|
|
88014
|
+
}
|
|
87337
88015
|
try {
|
|
87338
88016
|
doc2.replaceMarkerRegion("Tasks", table3);
|
|
87339
|
-
} catch {
|
|
88017
|
+
} catch (err) {
|
|
88018
|
+
if (!(err instanceof Error && err.message.startsWith("No marker region found")))
|
|
88019
|
+
throw err;
|
|
88020
|
+
skipped.push({ id: feature.id, reason: "no-tasks-marker-region" });
|
|
87340
88021
|
continue;
|
|
87341
88022
|
}
|
|
87342
88023
|
const next = doc2.serialize();
|
|
@@ -87345,7 +88026,7 @@ class FeatureService {
|
|
|
87345
88026
|
await atomicWriteAsync(feature.filePath, next, feature.id, this.ctx.fs, this.ctx.projectName ?? "spur");
|
|
87346
88027
|
tasksUpdated += 1;
|
|
87347
88028
|
}
|
|
87348
|
-
return { index: index2, tasksUpdated };
|
|
88029
|
+
return { index: index2, tasksUpdated, skipped };
|
|
87349
88030
|
}
|
|
87350
88031
|
async deriveFeatureStatus(featureId2) {
|
|
87351
88032
|
const feature = await this.show(featureId2);
|
|
@@ -87465,6 +88146,18 @@ class FeatureService {
|
|
|
87465
88146
|
};
|
|
87466
88147
|
}
|
|
87467
88148
|
async syncFeature(featureId2, options) {
|
|
88149
|
+
const suppressible = options?.force !== true && options?.dryRun !== true && options?.forceConfirm !== true;
|
|
88150
|
+
if (suppressible) {
|
|
88151
|
+
const replay = await this.replaySuppressedBlocked(featureId2);
|
|
88152
|
+
if (replay)
|
|
88153
|
+
return { ...replay, suppressed: true };
|
|
88154
|
+
}
|
|
88155
|
+
const result = await this.runSyncFeature(featureId2, options);
|
|
88156
|
+
if (options?.dryRun !== true)
|
|
88157
|
+
await this.recordBlockedSyncState(featureId2, result);
|
|
88158
|
+
return result;
|
|
88159
|
+
}
|
|
88160
|
+
async runSyncFeature(featureId2, options) {
|
|
87468
88161
|
const proposal = await this.deriveFeatureStatus(featureId2);
|
|
87469
88162
|
if (proposal.from === proposal.to) {
|
|
87470
88163
|
return { proposal, applied: false, appliedHops: [] };
|
|
@@ -87506,6 +88199,60 @@ class FeatureService {
|
|
|
87506
88199
|
}
|
|
87507
88200
|
return { proposal, applied: true, appliedHops };
|
|
87508
88201
|
}
|
|
88202
|
+
async replaySuppressedBlocked(featureId2) {
|
|
88203
|
+
const statePath = blockedStateFile(featureId2, defaultVerdictRunDir(this.ctx.tasksDir));
|
|
88204
|
+
let prior = null;
|
|
88205
|
+
try {
|
|
88206
|
+
if (await this.ctx.fs.exists(statePath))
|
|
88207
|
+
prior = parseBlockedState(await this.ctx.fs.readFile(statePath));
|
|
88208
|
+
} catch {
|
|
88209
|
+
return null;
|
|
88210
|
+
}
|
|
88211
|
+
if (!prior)
|
|
88212
|
+
return null;
|
|
88213
|
+
const inputs = await this.syncFingerprintInputs(featureId2);
|
|
88214
|
+
if (inputs === null)
|
|
88215
|
+
return null;
|
|
88216
|
+
return computeSyncFingerprint(inputs) === prior.inputFingerprint ? prior.result : null;
|
|
88217
|
+
}
|
|
88218
|
+
async recordBlockedSyncState(featureId2, result) {
|
|
88219
|
+
const runDir = defaultVerdictRunDir(this.ctx.tasksDir);
|
|
88220
|
+
const statePath = blockedStateFile(featureId2, runDir);
|
|
88221
|
+
const blocked = result.goalConflict === undefined && (result.proposal.gateBlocked === true || !result.applied && result.proposal.from !== result.proposal.to);
|
|
88222
|
+
if (!blocked) {
|
|
88223
|
+
try {
|
|
88224
|
+
if (await this.ctx.fs.exists(statePath))
|
|
88225
|
+
await this.ctx.fs.deleteFile(statePath);
|
|
88226
|
+
} catch {}
|
|
88227
|
+
return;
|
|
88228
|
+
}
|
|
88229
|
+
const inputs = await this.syncFingerprintInputs(featureId2);
|
|
88230
|
+
if (inputs === null)
|
|
88231
|
+
return;
|
|
88232
|
+
const state = {
|
|
88233
|
+
featureId: result.proposal.featureId,
|
|
88234
|
+
inputFingerprint: computeSyncFingerprint(inputs),
|
|
88235
|
+
proposal: result.proposal,
|
|
88236
|
+
classification: "blocked",
|
|
88237
|
+
result,
|
|
88238
|
+
persistedAt: new Date().toISOString()
|
|
88239
|
+
};
|
|
88240
|
+
try {
|
|
88241
|
+
await this.ctx.fs.ensureDir(runDir);
|
|
88242
|
+
await this.ctx.fs.writeFile(statePath, serializeBlockedState(state));
|
|
88243
|
+
} catch {}
|
|
88244
|
+
}
|
|
88245
|
+
async syncFingerprintInputs(featureId2) {
|
|
88246
|
+
const feature = await this.show(featureId2);
|
|
88247
|
+
if (!feature)
|
|
88248
|
+
return null;
|
|
88249
|
+
const tasks = (await this.collectTasksByFeature()).get(featureId2) ?? [];
|
|
88250
|
+
return {
|
|
88251
|
+
featureContentHash: createHash10("sha256").update(feature.content).digest("hex"),
|
|
88252
|
+
taskStatusVector: tasks.map((t) => `${t.wbs}:${t.status}`),
|
|
88253
|
+
verdictMtimeVector: await readVerdictMtimeVector(this.ctx.fs, defaultVerdictRunDir(this.ctx.tasksDir))
|
|
88254
|
+
};
|
|
88255
|
+
}
|
|
87509
88256
|
async findOneActiveGoalConflict(featureId2) {
|
|
87510
88257
|
const feature = await this.show(featureId2);
|
|
87511
88258
|
if (feature === null || feature.frontmatter.priority !== "P0")
|
|
@@ -87900,6 +88647,7 @@ var FEATURE_ACTION_NAMES, FEATURE_ACTION_COMMANDS, FEATURE_FILE_RE;
|
|
|
87900
88647
|
var init_feature_service = __esm(() => {
|
|
87901
88648
|
init_src2();
|
|
87902
88649
|
init_feature_check();
|
|
88650
|
+
init_feature_sync_suppression();
|
|
87903
88651
|
init_task_locator();
|
|
87904
88652
|
FEATURE_ACTION_NAMES = ["brainstorm", "plan"];
|
|
87905
88653
|
FEATURE_ACTION_COMMANDS = {
|
|
@@ -90846,8 +91594,182 @@ var init_history_service = __esm(() => {
|
|
|
90846
91594
|
HISTORY_BUSY_ERROR_PATTERN = /database is locked|SQLITE_BUSY/i;
|
|
90847
91595
|
});
|
|
90848
91596
|
|
|
91597
|
+
// ../../packages/app/src/workflow/action-trace.ts
|
|
91598
|
+
import { join as join26 } from "path";
|
|
91599
|
+
|
|
91600
|
+
class WorkflowActionTraceWriter {
|
|
91601
|
+
inner;
|
|
91602
|
+
recordFailure;
|
|
91603
|
+
db;
|
|
91604
|
+
boundaries = new Map;
|
|
91605
|
+
constructor(inner, recordFailure, db2) {
|
|
91606
|
+
this.inner = inner;
|
|
91607
|
+
this.recordFailure = recordFailure;
|
|
91608
|
+
this.db = db2;
|
|
91609
|
+
}
|
|
91610
|
+
async saveActionStart(runId, node, kind, options) {
|
|
91611
|
+
const outcome = await this.guard("action.start", { runId, node, kind }, () => this.inner.saveActionStart(runId, node, kind, options));
|
|
91612
|
+
if (!outcome.ok) {
|
|
91613
|
+
const syntheticId = `${UNPERSISTED_PREFIX}${crypto.randomUUID()}`;
|
|
91614
|
+
this.boundaries.set(syntheticId, { runId, node, kind });
|
|
91615
|
+
this.lastStart = { runId, node, kind };
|
|
91616
|
+
return syntheticId;
|
|
91617
|
+
}
|
|
91618
|
+
this.boundaries.set(outcome.value, { runId, node, kind });
|
|
91619
|
+
this.lastStart = { runId, node, kind };
|
|
91620
|
+
return outcome.value;
|
|
91621
|
+
}
|
|
91622
|
+
lastStart;
|
|
91623
|
+
async saveActionFinalize(actionId, status, durationMs, ok, kind, result, redactor) {
|
|
91624
|
+
const boundary2 = this.boundaries.get(actionId);
|
|
91625
|
+
const origin2 = boundary2 ?? this.lastStart;
|
|
91626
|
+
await this.guard("action.finish", { runId: origin2?.runId ?? "", node: origin2?.node, kind }, () => this.inner.saveActionFinalize(actionId, status, durationMs, ok, kind, result, redactor));
|
|
91627
|
+
this.boundaries.delete(actionId);
|
|
91628
|
+
}
|
|
91629
|
+
async finalizeRun(runId, status, completedAt, fence, reason) {
|
|
91630
|
+
await this.inner.finalizeRun(runId, status, completedAt, fence, reason === undefined ? undefined : classifyTerminalReason({ status, engineReason: reason, errorText: reason }));
|
|
91631
|
+
}
|
|
91632
|
+
async claimRunOwnership(runId, owner, expectedStatuses) {
|
|
91633
|
+
return this.inner.claimRunOwnership(runId, owner, expectedStatuses);
|
|
91634
|
+
}
|
|
91635
|
+
async interruptRun(runId, reason) {
|
|
91636
|
+
return this.inner.interruptRun(runId, reason);
|
|
91637
|
+
}
|
|
91638
|
+
async recordAction(boundary2) {
|
|
91639
|
+
const start = await this.guard("action.start", { runId: boundary2.runId, node: boundary2.node, kind: boundary2.kind }, () => this.inner.saveActionStart(boundary2.runId, boundary2.node, boundary2.kind));
|
|
91640
|
+
if (!start.ok)
|
|
91641
|
+
return start;
|
|
91642
|
+
const finish = await this.guard("action.finish", { runId: boundary2.runId, node: boundary2.node, kind: boundary2.kind }, () => this.inner.saveActionFinalize(start.value, boundary2.status, boundary2.durationMs, boundary2.ok, boundary2.kind, boundary2.result, boundary2.redactor));
|
|
91643
|
+
if (!finish.ok)
|
|
91644
|
+
return finish;
|
|
91645
|
+
await this.backdateStart(start.value, boundary2);
|
|
91646
|
+
return { ok: true, actionId: start.value };
|
|
91647
|
+
}
|
|
91648
|
+
async backdateStart(actionId, boundary2) {
|
|
91649
|
+
const db2 = this.db;
|
|
91650
|
+
if (db2 === undefined || boundary2.durationMs <= 0)
|
|
91651
|
+
return;
|
|
91652
|
+
await this.guard("action.backdate", { runId: boundary2.runId, node: boundary2.node, kind: boundary2.kind }, async () => {
|
|
91653
|
+
const dao2 = new ActionRunDao(db2);
|
|
91654
|
+
const completedAt = await dao2.completedAtById(actionId);
|
|
91655
|
+
if (completedAt === null)
|
|
91656
|
+
return;
|
|
91657
|
+
const startedAt = new Date(new Date(completedAt).getTime() - boundary2.durationMs).toISOString();
|
|
91658
|
+
await dao2.setStartedAt(actionId, startedAt);
|
|
91659
|
+
});
|
|
91660
|
+
}
|
|
91661
|
+
async closeRun(runId, status, completedAt, reason) {
|
|
91662
|
+
const existing = await this.inner.loadRun(runId);
|
|
91663
|
+
if (existing === undefined) {
|
|
91664
|
+
throw new RunRowNotFoundError(runId);
|
|
91665
|
+
}
|
|
91666
|
+
await this.inner.finalizeRun(runId, status, completedAt ?? new Date().toISOString(), undefined, reason === undefined ? undefined : classifyTerminalReason({ status, engineReason: reason, errorText: reason }));
|
|
91667
|
+
if (this.db === undefined)
|
|
91668
|
+
return { ok: true };
|
|
91669
|
+
const rows = await new ActionRunDao(this.db).actionRowsByRunId(runId);
|
|
91670
|
+
return { ok: true, actionRows: rows.length };
|
|
91671
|
+
}
|
|
91672
|
+
async guard(operation, context4, emit) {
|
|
91673
|
+
try {
|
|
91674
|
+
return { ok: true, value: await emit() };
|
|
91675
|
+
} catch (error51) {
|
|
91676
|
+
const failure = {
|
|
91677
|
+
operation,
|
|
91678
|
+
runId: context4.runId,
|
|
91679
|
+
...context4.node !== undefined ? { node: context4.node } : {},
|
|
91680
|
+
...context4.kind !== undefined ? { kind: context4.kind } : {},
|
|
91681
|
+
error: error51 instanceof Error ? error51.message : String(error51),
|
|
91682
|
+
at: new Date().toISOString()
|
|
91683
|
+
};
|
|
91684
|
+
try {
|
|
91685
|
+
this.recordFailure?.(failure);
|
|
91686
|
+
} catch {}
|
|
91687
|
+
return { ok: false, failure };
|
|
91688
|
+
}
|
|
91689
|
+
}
|
|
91690
|
+
createRun(record3) {
|
|
91691
|
+
return this.inner.createRun(record3);
|
|
91692
|
+
}
|
|
91693
|
+
savePhase(runId, phase, status) {
|
|
91694
|
+
return this.inner.savePhase(runId, phase, status);
|
|
91695
|
+
}
|
|
91696
|
+
saveTransition(runId, from, to, trigger) {
|
|
91697
|
+
return this.inner.saveTransition(runId, from, to, trigger);
|
|
91698
|
+
}
|
|
91699
|
+
saveWorkflowState(runId, state, data) {
|
|
91700
|
+
return this.inner.saveWorkflowState(runId, state, data);
|
|
91701
|
+
}
|
|
91702
|
+
commitTransition(runId, from, to, trigger, state, data, phase) {
|
|
91703
|
+
return this.inner.commitTransition(runId, from, to, trigger, state, data, phase);
|
|
91704
|
+
}
|
|
91705
|
+
loadRun(runId) {
|
|
91706
|
+
return this.inner.loadRun(runId);
|
|
91707
|
+
}
|
|
91708
|
+
listRuns() {
|
|
91709
|
+
return this.inner.listRuns();
|
|
91710
|
+
}
|
|
91711
|
+
findRunByKey(workflowName, externalKey) {
|
|
91712
|
+
return this.inner.findRunByKey(workflowName, externalKey);
|
|
91713
|
+
}
|
|
91714
|
+
createOrAttachRun(record3) {
|
|
91715
|
+
return this.inner.createOrAttachRun(record3);
|
|
91716
|
+
}
|
|
91717
|
+
reseedRun(runId, newState) {
|
|
91718
|
+
return this.inner.reseedRun(runId, newState);
|
|
91719
|
+
}
|
|
91720
|
+
loadCurrentState(runId) {
|
|
91721
|
+
return this.inner.loadCurrentState(runId);
|
|
91722
|
+
}
|
|
91723
|
+
loadLatestStateSnapshot(runId) {
|
|
91724
|
+
return this.inner.loadLatestStateSnapshot(runId);
|
|
91725
|
+
}
|
|
91726
|
+
listPausedRuns(options) {
|
|
91727
|
+
return this.inner.listPausedRuns(options);
|
|
91728
|
+
}
|
|
91729
|
+
}
|
|
91730
|
+
function withActionTrace(persistence2, recordFailure) {
|
|
91731
|
+
return new WorkflowActionTraceWriter(persistence2, recordFailure);
|
|
91732
|
+
}
|
|
91733
|
+
function createWorkflowActionTraceWriter(db2, recordFailure) {
|
|
91734
|
+
return new WorkflowActionTraceWriter(new DbWorkflowPersistenceAdapter(db2), recordFailure, db2);
|
|
91735
|
+
}
|
|
91736
|
+
function createRunLogTraceFailureRecorder(cwd) {
|
|
91737
|
+
const fileSystem = createNodeFileSystem3();
|
|
91738
|
+
return (failure) => {
|
|
91739
|
+
const safeRunId = failure.runId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
91740
|
+
const dir = join26(cwd, ".spur", "run");
|
|
91741
|
+
const location = [
|
|
91742
|
+
failure.node !== undefined ? `node=${failure.node}` : "",
|
|
91743
|
+
failure.kind !== undefined ? `kind=${failure.kind}` : ""
|
|
91744
|
+
].filter((part) => part !== "").join(" ");
|
|
91745
|
+
const line = `[${failure.at.replace(/\.\d{3}Z$/, "Z")}] trace-emission-failed operation=${failure.operation} run=${failure.runId}${location === "" ? "" : ` ${location}`}: ${failure.error}
|
|
91746
|
+
`;
|
|
91747
|
+
(async () => {
|
|
91748
|
+
await fileSystem.ensureDir(dir);
|
|
91749
|
+
await fileSystem.appendFile(join26(dir, `${safeRunId}.log`), line);
|
|
91750
|
+
})().catch(() => {
|
|
91751
|
+
return;
|
|
91752
|
+
});
|
|
91753
|
+
};
|
|
91754
|
+
}
|
|
91755
|
+
var UNPERSISTED_PREFIX = "trace-unpersisted:", RunRowNotFoundError;
|
|
91756
|
+
var init_action_trace = __esm(() => {
|
|
91757
|
+
init_src2();
|
|
91758
|
+
init_dist8();
|
|
91759
|
+
init_dist5();
|
|
91760
|
+
init_terminal_reason();
|
|
91761
|
+
RunRowNotFoundError = class RunRowNotFoundError extends Error {
|
|
91762
|
+
runId;
|
|
91763
|
+
constructor(runId) {
|
|
91764
|
+
super(`run row not found: ${runId}`);
|
|
91765
|
+
this.name = "RunRowNotFoundError";
|
|
91766
|
+
this.runId = runId;
|
|
91767
|
+
}
|
|
91768
|
+
};
|
|
91769
|
+
});
|
|
91770
|
+
|
|
90849
91771
|
// ../../packages/app/src/workflow/decision-evidence.ts
|
|
90850
|
-
import { createHash as
|
|
91772
|
+
import { createHash as createHash11 } from "crypto";
|
|
90851
91773
|
function selectEvidence(allRows, producerNodes, cleaner) {
|
|
90852
91774
|
if (producerNodes.length > MAX_EVIDENCE_ROWS) {
|
|
90853
91775
|
return { ok: false, reason: "oversized-evidence" };
|
|
@@ -90926,7 +91848,7 @@ function parseSummaryEnvelope(raw, runId, evidenceNodes, selected, cleaner) {
|
|
|
90926
91848
|
return { ok: true, summary };
|
|
90927
91849
|
}
|
|
90928
91850
|
function evidencePayloadDigest(payload) {
|
|
90929
|
-
return `sha256:${
|
|
91851
|
+
return `sha256:${createHash11("sha256").update(canonicalJsonStringify(payload), "utf8").digest("hex")}`;
|
|
90930
91852
|
}
|
|
90931
91853
|
function outcomeText(raw, clean) {
|
|
90932
91854
|
if (!raw)
|
|
@@ -91370,7 +92292,7 @@ var init_decide = __esm(() => {
|
|
|
91370
92292
|
});
|
|
91371
92293
|
|
|
91372
92294
|
// ../../packages/app/src/workflow/actions/decide.ts
|
|
91373
|
-
import { join as
|
|
92295
|
+
import { join as join27 } from "path";
|
|
91374
92296
|
|
|
91375
92297
|
class DecideActionRunner {
|
|
91376
92298
|
fileSystem;
|
|
@@ -91392,15 +92314,15 @@ class DecideActionRunner {
|
|
|
91392
92314
|
const workdir = context4.workdir ?? ".";
|
|
91393
92315
|
const result = await runDecide({
|
|
91394
92316
|
...options,
|
|
91395
|
-
...options.evidence !== undefined ? { evidence: options.evidence.map((path9) =>
|
|
91396
|
-
resultFile:
|
|
92317
|
+
...options.evidence !== undefined ? { evidence: options.evidence.map((path9) => join27(workdir, path9)) } : {},
|
|
92318
|
+
resultFile: join27(workdir, options.resultFile)
|
|
91397
92319
|
}, {
|
|
91398
92320
|
enabled: this.deps.enabled,
|
|
91399
92321
|
...this.deps.decisionMaker !== undefined ? { decisionMaker: this.deps.decisionMaker } : {},
|
|
91400
92322
|
readFile: async (path9) => await this.fileSystem.readFile(path9),
|
|
91401
92323
|
now: Date.now.bind(Date)
|
|
91402
92324
|
});
|
|
91403
|
-
await this.fileSystem.writeFile(
|
|
92325
|
+
await this.fileSystem.writeFile(join27(workdir, options.resultFile), `${JSON.stringify(result, null, 2)}
|
|
91404
92326
|
`);
|
|
91405
92327
|
return { ok: true, data: { value: result.value, decision: result } };
|
|
91406
92328
|
}
|
|
@@ -91516,180 +92438,6 @@ function renderEventTrace(events2) {
|
|
|
91516
92438
|
`);
|
|
91517
92439
|
}
|
|
91518
92440
|
|
|
91519
|
-
// ../../packages/app/src/workflow/action-trace.ts
|
|
91520
|
-
import { join as join27 } from "path";
|
|
91521
|
-
|
|
91522
|
-
class WorkflowActionTraceWriter {
|
|
91523
|
-
inner;
|
|
91524
|
-
recordFailure;
|
|
91525
|
-
db;
|
|
91526
|
-
boundaries = new Map;
|
|
91527
|
-
constructor(inner, recordFailure, db2) {
|
|
91528
|
-
this.inner = inner;
|
|
91529
|
-
this.recordFailure = recordFailure;
|
|
91530
|
-
this.db = db2;
|
|
91531
|
-
}
|
|
91532
|
-
async saveActionStart(runId, node, kind, options) {
|
|
91533
|
-
const outcome = await this.guard("action.start", { runId, node, kind }, () => this.inner.saveActionStart(runId, node, kind, options));
|
|
91534
|
-
if (!outcome.ok) {
|
|
91535
|
-
const syntheticId = `${UNPERSISTED_PREFIX}${crypto.randomUUID()}`;
|
|
91536
|
-
this.boundaries.set(syntheticId, { runId, node, kind });
|
|
91537
|
-
this.lastStart = { runId, node, kind };
|
|
91538
|
-
return syntheticId;
|
|
91539
|
-
}
|
|
91540
|
-
this.boundaries.set(outcome.value, { runId, node, kind });
|
|
91541
|
-
this.lastStart = { runId, node, kind };
|
|
91542
|
-
return outcome.value;
|
|
91543
|
-
}
|
|
91544
|
-
lastStart;
|
|
91545
|
-
async saveActionFinalize(actionId, status, durationMs, ok, kind, result, redactor) {
|
|
91546
|
-
const boundary2 = this.boundaries.get(actionId);
|
|
91547
|
-
const origin2 = boundary2 ?? this.lastStart;
|
|
91548
|
-
await this.guard("action.finish", { runId: origin2?.runId ?? "", node: origin2?.node, kind }, () => this.inner.saveActionFinalize(actionId, status, durationMs, ok, kind, result, redactor));
|
|
91549
|
-
this.boundaries.delete(actionId);
|
|
91550
|
-
}
|
|
91551
|
-
async finalizeRun(runId, status, completedAt, fence, reason) {
|
|
91552
|
-
await this.inner.finalizeRun(runId, status, completedAt, fence, reason === undefined ? undefined : classifyTerminalReason({ status, engineReason: reason, errorText: reason }));
|
|
91553
|
-
}
|
|
91554
|
-
async claimRunOwnership(runId, owner, expectedStatuses) {
|
|
91555
|
-
return this.inner.claimRunOwnership(runId, owner, expectedStatuses);
|
|
91556
|
-
}
|
|
91557
|
-
async interruptRun(runId, reason) {
|
|
91558
|
-
return this.inner.interruptRun(runId, reason);
|
|
91559
|
-
}
|
|
91560
|
-
async recordAction(boundary2) {
|
|
91561
|
-
const start = await this.guard("action.start", { runId: boundary2.runId, node: boundary2.node, kind: boundary2.kind }, () => this.inner.saveActionStart(boundary2.runId, boundary2.node, boundary2.kind));
|
|
91562
|
-
if (!start.ok)
|
|
91563
|
-
return start;
|
|
91564
|
-
const finish = await this.guard("action.finish", { runId: boundary2.runId, node: boundary2.node, kind: boundary2.kind }, () => this.inner.saveActionFinalize(start.value, boundary2.status, boundary2.durationMs, boundary2.ok, boundary2.kind, boundary2.result, boundary2.redactor));
|
|
91565
|
-
if (!finish.ok)
|
|
91566
|
-
return finish;
|
|
91567
|
-
await this.backdateStart(start.value, boundary2);
|
|
91568
|
-
return { ok: true, actionId: start.value };
|
|
91569
|
-
}
|
|
91570
|
-
async backdateStart(actionId, boundary2) {
|
|
91571
|
-
const db2 = this.db;
|
|
91572
|
-
if (db2 === undefined || boundary2.durationMs <= 0)
|
|
91573
|
-
return;
|
|
91574
|
-
await this.guard("action.backdate", { runId: boundary2.runId, node: boundary2.node, kind: boundary2.kind }, async () => {
|
|
91575
|
-
const dao2 = new ActionRunDao(db2);
|
|
91576
|
-
const completedAt = await dao2.completedAtById(actionId);
|
|
91577
|
-
if (completedAt === null)
|
|
91578
|
-
return;
|
|
91579
|
-
const startedAt = new Date(new Date(completedAt).getTime() - boundary2.durationMs).toISOString();
|
|
91580
|
-
await dao2.setStartedAt(actionId, startedAt);
|
|
91581
|
-
});
|
|
91582
|
-
}
|
|
91583
|
-
async closeRun(runId, status, completedAt, reason) {
|
|
91584
|
-
const existing = await this.inner.loadRun(runId);
|
|
91585
|
-
if (existing === undefined) {
|
|
91586
|
-
throw new RunRowNotFoundError(runId);
|
|
91587
|
-
}
|
|
91588
|
-
await this.inner.finalizeRun(runId, status, completedAt ?? new Date().toISOString(), undefined, reason === undefined ? undefined : classifyTerminalReason({ status, engineReason: reason, errorText: reason }));
|
|
91589
|
-
if (this.db === undefined)
|
|
91590
|
-
return { ok: true };
|
|
91591
|
-
const rows = await new ActionRunDao(this.db).actionRowsByRunId(runId);
|
|
91592
|
-
return { ok: true, actionRows: rows.length };
|
|
91593
|
-
}
|
|
91594
|
-
async guard(operation, context4, emit) {
|
|
91595
|
-
try {
|
|
91596
|
-
return { ok: true, value: await emit() };
|
|
91597
|
-
} catch (error51) {
|
|
91598
|
-
const failure = {
|
|
91599
|
-
operation,
|
|
91600
|
-
runId: context4.runId,
|
|
91601
|
-
...context4.node !== undefined ? { node: context4.node } : {},
|
|
91602
|
-
...context4.kind !== undefined ? { kind: context4.kind } : {},
|
|
91603
|
-
error: error51 instanceof Error ? error51.message : String(error51),
|
|
91604
|
-
at: new Date().toISOString()
|
|
91605
|
-
};
|
|
91606
|
-
try {
|
|
91607
|
-
this.recordFailure?.(failure);
|
|
91608
|
-
} catch {}
|
|
91609
|
-
return { ok: false, failure };
|
|
91610
|
-
}
|
|
91611
|
-
}
|
|
91612
|
-
createRun(record3) {
|
|
91613
|
-
return this.inner.createRun(record3);
|
|
91614
|
-
}
|
|
91615
|
-
savePhase(runId, phase, status) {
|
|
91616
|
-
return this.inner.savePhase(runId, phase, status);
|
|
91617
|
-
}
|
|
91618
|
-
saveTransition(runId, from, to, trigger) {
|
|
91619
|
-
return this.inner.saveTransition(runId, from, to, trigger);
|
|
91620
|
-
}
|
|
91621
|
-
saveWorkflowState(runId, state, data) {
|
|
91622
|
-
return this.inner.saveWorkflowState(runId, state, data);
|
|
91623
|
-
}
|
|
91624
|
-
commitTransition(runId, from, to, trigger, state, data, phase) {
|
|
91625
|
-
return this.inner.commitTransition(runId, from, to, trigger, state, data, phase);
|
|
91626
|
-
}
|
|
91627
|
-
loadRun(runId) {
|
|
91628
|
-
return this.inner.loadRun(runId);
|
|
91629
|
-
}
|
|
91630
|
-
listRuns() {
|
|
91631
|
-
return this.inner.listRuns();
|
|
91632
|
-
}
|
|
91633
|
-
findRunByKey(workflowName, externalKey) {
|
|
91634
|
-
return this.inner.findRunByKey(workflowName, externalKey);
|
|
91635
|
-
}
|
|
91636
|
-
createOrAttachRun(record3) {
|
|
91637
|
-
return this.inner.createOrAttachRun(record3);
|
|
91638
|
-
}
|
|
91639
|
-
reseedRun(runId, newState) {
|
|
91640
|
-
return this.inner.reseedRun(runId, newState);
|
|
91641
|
-
}
|
|
91642
|
-
loadCurrentState(runId) {
|
|
91643
|
-
return this.inner.loadCurrentState(runId);
|
|
91644
|
-
}
|
|
91645
|
-
loadLatestStateSnapshot(runId) {
|
|
91646
|
-
return this.inner.loadLatestStateSnapshot(runId);
|
|
91647
|
-
}
|
|
91648
|
-
listPausedRuns(options) {
|
|
91649
|
-
return this.inner.listPausedRuns(options);
|
|
91650
|
-
}
|
|
91651
|
-
}
|
|
91652
|
-
function withActionTrace(persistence2, recordFailure) {
|
|
91653
|
-
return new WorkflowActionTraceWriter(persistence2, recordFailure);
|
|
91654
|
-
}
|
|
91655
|
-
function createWorkflowActionTraceWriter(db2, recordFailure) {
|
|
91656
|
-
return new WorkflowActionTraceWriter(new DbWorkflowPersistenceAdapter(db2), recordFailure, db2);
|
|
91657
|
-
}
|
|
91658
|
-
function createRunLogTraceFailureRecorder(cwd) {
|
|
91659
|
-
const fileSystem = createNodeFileSystem3();
|
|
91660
|
-
return (failure) => {
|
|
91661
|
-
const safeRunId = failure.runId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
91662
|
-
const dir = join27(cwd, ".spur", "run");
|
|
91663
|
-
const location = [
|
|
91664
|
-
failure.node !== undefined ? `node=${failure.node}` : "",
|
|
91665
|
-
failure.kind !== undefined ? `kind=${failure.kind}` : ""
|
|
91666
|
-
].filter((part) => part !== "").join(" ");
|
|
91667
|
-
const line = `[${failure.at.replace(/\.\d{3}Z$/, "Z")}] trace-emission-failed operation=${failure.operation} run=${failure.runId}${location === "" ? "" : ` ${location}`}: ${failure.error}
|
|
91668
|
-
`;
|
|
91669
|
-
(async () => {
|
|
91670
|
-
await fileSystem.ensureDir(dir);
|
|
91671
|
-
await fileSystem.appendFile(join27(dir, `${safeRunId}.log`), line);
|
|
91672
|
-
})().catch(() => {
|
|
91673
|
-
return;
|
|
91674
|
-
});
|
|
91675
|
-
};
|
|
91676
|
-
}
|
|
91677
|
-
var UNPERSISTED_PREFIX = "trace-unpersisted:", RunRowNotFoundError;
|
|
91678
|
-
var init_action_trace = __esm(() => {
|
|
91679
|
-
init_src2();
|
|
91680
|
-
init_dist8();
|
|
91681
|
-
init_dist5();
|
|
91682
|
-
init_terminal_reason();
|
|
91683
|
-
RunRowNotFoundError = class RunRowNotFoundError extends Error {
|
|
91684
|
-
runId;
|
|
91685
|
-
constructor(runId) {
|
|
91686
|
-
super(`run row not found: ${runId}`);
|
|
91687
|
-
this.name = "RunRowNotFoundError";
|
|
91688
|
-
this.runId = runId;
|
|
91689
|
-
}
|
|
91690
|
-
};
|
|
91691
|
-
});
|
|
91692
|
-
|
|
91693
92441
|
// ../../packages/app/src/workflow/actions/run-path.ts
|
|
91694
92442
|
import { dirname as dirname17, join as join28, normalize, relative as relative4, resolve as resolve10, sep as sep5 } from "path";
|
|
91695
92443
|
function within(child, ancestor) {
|
|
@@ -97171,7 +97919,7 @@ var init_workflow_service = __esm(() => {
|
|
|
97171
97919
|
});
|
|
97172
97920
|
|
|
97173
97921
|
// ../../packages/app/src/services/inline-run-setup.ts
|
|
97174
|
-
import { join as join33, resolve as resolve14 } from "path";
|
|
97922
|
+
import { basename as basename12, join as join33, resolve as resolve14 } from "path";
|
|
97175
97923
|
function parseIdentityMetadata(raw) {
|
|
97176
97924
|
if (raw.trim() === "")
|
|
97177
97925
|
return {};
|
|
@@ -97219,6 +97967,43 @@ async function persistWorktreeRuns(input) {
|
|
|
97219
97967
|
citedNames.add(name);
|
|
97220
97968
|
}
|
|
97221
97969
|
}
|
|
97970
|
+
if ((input.taskFiles ?? []).length > 0) {
|
|
97971
|
+
const prefixes = (input.taskFiles ?? []).flatMap((taskFile) => {
|
|
97972
|
+
const wbs = /^(\d{4})_/.exec(basename12(taskFile))?.[1];
|
|
97973
|
+
return wbs === undefined ? [] : [`${wbs}-`];
|
|
97974
|
+
});
|
|
97975
|
+
const recordNames = new Set;
|
|
97976
|
+
const owner = await openInlineRunProjectDb(fromDir);
|
|
97977
|
+
try {
|
|
97978
|
+
for (const row of await listRunIdRows(owner.adapter)) {
|
|
97979
|
+
if (!SAFE_RUN_ID_RE.test(row.id))
|
|
97980
|
+
throw new InvalidWorkflowRunIdError(row.id);
|
|
97981
|
+
prefixes.push(`${row.id}-`);
|
|
97982
|
+
recordNames.add(`${row.id}.md`).add(`${row.id}.state.json`);
|
|
97983
|
+
}
|
|
97984
|
+
} finally {
|
|
97985
|
+
owner.close();
|
|
97986
|
+
}
|
|
97987
|
+
const entries = await readdir2(fromRunDir).catch((error51) => {
|
|
97988
|
+
if (error51.code === "ENOENT")
|
|
97989
|
+
return [];
|
|
97990
|
+
throw error51;
|
|
97991
|
+
});
|
|
97992
|
+
const ownedCounts = new Map;
|
|
97993
|
+
for (const name of entries.sort()) {
|
|
97994
|
+
if (citedNames.has(name) || recordNames.has(name))
|
|
97995
|
+
continue;
|
|
97996
|
+
const owner2 = prefixes.find((prefix) => name.startsWith(prefix));
|
|
97997
|
+
if (owner2 === undefined)
|
|
97998
|
+
continue;
|
|
97999
|
+
const count3 = (ownedCounts.get(owner2) ?? 0) + 1;
|
|
98000
|
+
if (count3 > MAX_CITED_RUN_FILES) {
|
|
98001
|
+
throw new Error(`persist-out: owner ${owner2} owns more than ${MAX_CITED_RUN_FILES} .spur/run/ files ` + "\u2014 over the per-owner evidence cap (1012 R1, 1034 R1); split the batch or prune the evidence");
|
|
98002
|
+
}
|
|
98003
|
+
ownedCounts.set(owner2, count3);
|
|
98004
|
+
citedNames.add(name);
|
|
98005
|
+
}
|
|
98006
|
+
}
|
|
97222
98007
|
const citedCopies = [];
|
|
97223
98008
|
const citedSkips = [];
|
|
97224
98009
|
for (const name of citedNames) {
|
|
@@ -97488,20 +98273,334 @@ async function runDecideForInlineRun(input) {
|
|
|
97488
98273
|
durationMs: decision.durationMs
|
|
97489
98274
|
};
|
|
97490
98275
|
}
|
|
97491
|
-
|
|
98276
|
+
function writeInlineRunOutcome(runId, outcome) {
|
|
98277
|
+
const runDir = join33(process.cwd(), ".spur", "run");
|
|
98278
|
+
if (!existsSync8(runDir))
|
|
98279
|
+
mkdirSync8(runDir, { recursive: true });
|
|
98280
|
+
const statePath = join33(runDir, `${runId}.state.json`);
|
|
98281
|
+
const markdownPath = join33(runDir, `${runId}.md`);
|
|
98282
|
+
let prior = {};
|
|
98283
|
+
try {
|
|
98284
|
+
const parsed = JSON.parse(readFileSync12(statePath, "utf8"));
|
|
98285
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
98286
|
+
prior = parsed;
|
|
98287
|
+
}
|
|
98288
|
+
} catch {}
|
|
98289
|
+
const at2 = new Date().toISOString();
|
|
98290
|
+
const state = {
|
|
98291
|
+
schemaVersion: 1,
|
|
98292
|
+
runId,
|
|
98293
|
+
...outcome.workflowName !== undefined ? { workflowName: outcome.workflowName } : {},
|
|
98294
|
+
...outcome.status !== undefined ? { status: outcome.status } : {},
|
|
98295
|
+
startedAt: typeof prior.startedAt === "string" ? prior.startedAt : at2,
|
|
98296
|
+
updatedAt: at2,
|
|
98297
|
+
...outcome.attached !== undefined ? { attached: outcome.attached } : {},
|
|
98298
|
+
...outcome.definitionDigest !== undefined ? { definitionDigest: outcome.definitionDigest } : {},
|
|
98299
|
+
...outcome.workflowVersion !== undefined ? { workflowVersion: outcome.workflowVersion } : {},
|
|
98300
|
+
...outcome.resolvedPath !== undefined ? { resolvedPath: outcome.resolvedPath } : {},
|
|
98301
|
+
...outcome.layer !== undefined ? { layer: outcome.layer } : {},
|
|
98302
|
+
...outcome.workdir !== undefined ? { workdir: outcome.workdir } : {},
|
|
98303
|
+
ok: outcome.ok,
|
|
98304
|
+
...outcome.ok === false && outcome.error !== undefined ? { error: outcome.error } : {}
|
|
98305
|
+
};
|
|
98306
|
+
const temp = `${statePath}.tmp`;
|
|
98307
|
+
try {
|
|
98308
|
+
writeFileSync7(temp, `${JSON.stringify(state, null, 4)}
|
|
98309
|
+
`);
|
|
98310
|
+
renameSync6(temp, statePath);
|
|
98311
|
+
} catch {
|
|
98312
|
+
try {
|
|
98313
|
+
unlinkSync4(temp);
|
|
98314
|
+
} catch {}
|
|
98315
|
+
}
|
|
98316
|
+
let headerFd;
|
|
98317
|
+
try {
|
|
98318
|
+
headerFd = openSync4(markdownPath, "wx");
|
|
98319
|
+
writeSync3(headerFd, `# spur inline run ${runId} \u2014 ${outcome.workflowName ?? "unknown workflow"} \u2014 setup ${at2}
|
|
98320
|
+
`);
|
|
98321
|
+
} catch {} finally {
|
|
98322
|
+
if (headerFd !== undefined)
|
|
98323
|
+
closeSync4(headerFd);
|
|
98324
|
+
}
|
|
98325
|
+
}
|
|
98326
|
+
function isInlineRunCloseStatus(status) {
|
|
98327
|
+
return CLOSE_STATUSES.has(status);
|
|
98328
|
+
}
|
|
98329
|
+
function isInlineRunActionStatus(status) {
|
|
98330
|
+
return ACTION_STATUSES.has(status);
|
|
98331
|
+
}
|
|
98332
|
+
function inlineRunRecordLogPath(runDir, runId) {
|
|
98333
|
+
const markdownPath = join33(runDir, `${runId}.md`);
|
|
98334
|
+
const legacyLogPath = join33(runDir, `${runId}.log`);
|
|
98335
|
+
if (existsSync8(legacyLogPath) && !existsSync8(markdownPath))
|
|
98336
|
+
return legacyLogPath;
|
|
98337
|
+
return markdownPath;
|
|
98338
|
+
}
|
|
98339
|
+
function appendInlineRunLogLine(runId, detail) {
|
|
98340
|
+
try {
|
|
98341
|
+
const runDir = join33(process.cwd(), ".spur", "run");
|
|
98342
|
+
if (!existsSync8(runDir))
|
|
98343
|
+
mkdirSync8(runDir, { recursive: true });
|
|
98344
|
+
const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
98345
|
+
const stamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
98346
|
+
appendFileSync5(inlineRunRecordLogPath(runDir, safeRunId), `[${stamp}] ${detail}
|
|
98347
|
+
`);
|
|
98348
|
+
} catch {}
|
|
98349
|
+
}
|
|
98350
|
+
async function runInlineRunTrace(input) {
|
|
98351
|
+
const operation = input.close ? "run.close" : "action.finish";
|
|
98352
|
+
const fail = (error51) => {
|
|
98353
|
+
appendInlineRunLogLine(input.runId, `trace-emission-failed operation=${operation} run=${input.runId}${input.node === "" ? "" : ` node=${input.node}`}${input.kind === "" ? "" : ` kind=${input.kind}`}: ${error51}`);
|
|
98354
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error: error51 })}
|
|
98355
|
+
`);
|
|
98356
|
+
return input.close ? 1 : 0;
|
|
98357
|
+
};
|
|
98358
|
+
let projectDb;
|
|
98359
|
+
try {
|
|
98360
|
+
projectDb = await openInlineRunProjectDb(process.cwd());
|
|
98361
|
+
const writer = createWorkflowActionTraceWriter(projectDb.adapter, (failure) => {
|
|
98362
|
+
const detail = failure;
|
|
98363
|
+
appendInlineRunLogLine(input.runId, `trace-emission-failed operation=${detail.operation ?? operation} run=${input.runId}: ${detail.error ?? "unknown error"}`);
|
|
98364
|
+
});
|
|
98365
|
+
const result = input.close ? await writer.closeRun(input.runId, input.status, undefined, input.reason) : await writer.recordAction({
|
|
98366
|
+
runId: input.runId,
|
|
98367
|
+
node: input.node,
|
|
98368
|
+
kind: input.kind,
|
|
98369
|
+
status: input.status,
|
|
98370
|
+
ok: input.ok,
|
|
98371
|
+
durationMs: input.durationMs
|
|
98372
|
+
});
|
|
98373
|
+
if (result.ok !== true && result.failure !== undefined) {
|
|
98374
|
+
const failure = result.failure;
|
|
98375
|
+
return fail(failure.error ?? "unknown trace emission failure");
|
|
98376
|
+
}
|
|
98377
|
+
if (input.close && input.status === "done" && result.actionRows === 0) {
|
|
98378
|
+
const error51 = `run ${input.runId} closed done with zero action_runs rows`;
|
|
98379
|
+
appendInlineRunLogLine(input.runId, `trace-close-failed run=${input.runId}: ${error51}`);
|
|
98380
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error: error51, code: "NO_ACTION_ROWS", actionRows: 0 })}
|
|
98381
|
+
`);
|
|
98382
|
+
return 1;
|
|
98383
|
+
}
|
|
98384
|
+
process.stdout.write(`${JSON.stringify({ ...result, runId: input.runId })}
|
|
98385
|
+
`);
|
|
98386
|
+
return 0;
|
|
98387
|
+
} catch (error51) {
|
|
98388
|
+
if (input.close && error51.name === "RunRowNotFoundError") {
|
|
98389
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
98390
|
+
appendInlineRunLogLine(input.runId, `trace-close-failed run=${input.runId}: ${message}`);
|
|
98391
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error: message, code: "RUN_NOT_FOUND" })}
|
|
98392
|
+
`);
|
|
98393
|
+
return 1;
|
|
98394
|
+
}
|
|
98395
|
+
return fail(error51 instanceof Error ? error51.message : String(error51));
|
|
98396
|
+
} finally {
|
|
98397
|
+
projectDb?.close();
|
|
98398
|
+
}
|
|
98399
|
+
}
|
|
98400
|
+
async function runInlineRunTraceBatch(input) {
|
|
98401
|
+
const batchFailed = (error51) => {
|
|
98402
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error: error51 })}
|
|
98403
|
+
`);
|
|
98404
|
+
return 1;
|
|
98405
|
+
};
|
|
98406
|
+
let rows;
|
|
98407
|
+
try {
|
|
98408
|
+
rows = JSON.parse(readFileSync12(input.actionsFile, "utf8"));
|
|
98409
|
+
} catch (error51) {
|
|
98410
|
+
return batchFailed(`cannot read actions file ${input.actionsFile}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
98411
|
+
}
|
|
98412
|
+
if (!Array.isArray(rows)) {
|
|
98413
|
+
return batchFailed("actions file must be a JSON array of {node,kind,status,ok,durationMs}");
|
|
98414
|
+
}
|
|
98415
|
+
const entries = [];
|
|
98416
|
+
const rowError = (index2, error51) => `actions[${index2}]: ${error51}`;
|
|
98417
|
+
for (const [index2, row] of rows.entries()) {
|
|
98418
|
+
if (row === null || typeof row !== "object" || Array.isArray(row)) {
|
|
98419
|
+
return batchFailed(rowError(index2, "entry must be a JSON object"));
|
|
98420
|
+
}
|
|
98421
|
+
const record3 = row;
|
|
98422
|
+
const { node, kind, status, ok, durationMs } = record3;
|
|
98423
|
+
if (typeof node !== "string" || node.trim() === "") {
|
|
98424
|
+
return batchFailed(rowError(index2, "node must be a non-empty string"));
|
|
98425
|
+
}
|
|
98426
|
+
if (typeof kind !== "string" || kind.trim() === "") {
|
|
98427
|
+
return batchFailed(rowError(index2, "kind must be a non-empty string"));
|
|
98428
|
+
}
|
|
98429
|
+
if (typeof status !== "string" || !isInlineRunActionStatus(status)) {
|
|
98430
|
+
return batchFailed(rowError(index2, 'status must be "done" or "failed"'));
|
|
98431
|
+
}
|
|
98432
|
+
if (typeof ok !== "boolean")
|
|
98433
|
+
return batchFailed(rowError(index2, "ok must be a boolean"));
|
|
98434
|
+
if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0) {
|
|
98435
|
+
return batchFailed(rowError(index2, "durationMs must be a finite non-negative number"));
|
|
98436
|
+
}
|
|
98437
|
+
entries.push({ node, kind, status, ok, durationMs });
|
|
98438
|
+
}
|
|
98439
|
+
let projectDb;
|
|
98440
|
+
let recorded = 0;
|
|
98441
|
+
const reportEmissionFailure = (node, kind, error51) => {
|
|
98442
|
+
appendInlineRunLogLine(input.runId, `trace-emission-failed operation=action.finish run=${input.runId} node=${node} kind=${kind}: ${error51}`);
|
|
98443
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, recorded, error: error51 })}
|
|
98444
|
+
`);
|
|
98445
|
+
};
|
|
98446
|
+
try {
|
|
98447
|
+
projectDb = await openInlineRunProjectDb(process.cwd());
|
|
98448
|
+
const writer = createWorkflowActionTraceWriter(projectDb.adapter, (failure) => {
|
|
98449
|
+
const detail = failure;
|
|
98450
|
+
appendInlineRunLogLine(input.runId, `trace-emission-failed operation=${detail.operation ?? "action.finish"} run=${input.runId}: ${detail.error ?? "unknown error"}`);
|
|
98451
|
+
});
|
|
98452
|
+
for (const entry of entries) {
|
|
98453
|
+
const result = await writer.recordAction({
|
|
98454
|
+
runId: input.runId,
|
|
98455
|
+
node: entry.node,
|
|
98456
|
+
kind: entry.kind,
|
|
98457
|
+
status: entry.status,
|
|
98458
|
+
ok: entry.ok,
|
|
98459
|
+
durationMs: entry.durationMs
|
|
98460
|
+
});
|
|
98461
|
+
if (result.ok === true) {
|
|
98462
|
+
recorded += 1;
|
|
98463
|
+
continue;
|
|
98464
|
+
}
|
|
98465
|
+
const failure = result.failure;
|
|
98466
|
+
reportEmissionFailure(entry.node, entry.kind, failure?.error ?? "unknown trace emission failure");
|
|
98467
|
+
return 0;
|
|
98468
|
+
}
|
|
98469
|
+
process.stdout.write(`${JSON.stringify({ ok: true, runId: input.runId, recorded })}
|
|
98470
|
+
`);
|
|
98471
|
+
return 0;
|
|
98472
|
+
} catch (error51) {
|
|
98473
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
98474
|
+
appendInlineRunLogLine(input.runId, `trace-emission-failed run=${input.runId}: ${message}`);
|
|
98475
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, recorded, error: message })}
|
|
98476
|
+
`);
|
|
98477
|
+
return 0;
|
|
98478
|
+
} finally {
|
|
98479
|
+
projectDb?.close();
|
|
98480
|
+
}
|
|
98481
|
+
}
|
|
98482
|
+
async function runInlineRunFingerprint(input) {
|
|
98483
|
+
const workdir = process.cwd();
|
|
98484
|
+
const contents = await readProofInputContents(undefined, workdir, {
|
|
98485
|
+
taskFile: input.taskFile,
|
|
98486
|
+
...input.featureFile !== undefined && input.featureFile.trim() !== "" ? { featureFile: input.featureFile } : {}
|
|
98487
|
+
});
|
|
98488
|
+
if (!contents.ok) {
|
|
98489
|
+
console.error(`inline-run-setup: FAIL \u2014 ${contents.error}`);
|
|
98490
|
+
return 1;
|
|
98491
|
+
}
|
|
98492
|
+
const digest = await computeProofInputFingerprint({
|
|
98493
|
+
cwd: workdir,
|
|
98494
|
+
...contents.taskContent !== undefined ? { taskContent: contents.taskContent } : {},
|
|
98495
|
+
...contents.featureContent !== undefined ? { featureContent: contents.featureContent } : {}
|
|
98496
|
+
});
|
|
98497
|
+
process.stdout.write(`${digest}
|
|
98498
|
+
`);
|
|
98499
|
+
return 0;
|
|
98500
|
+
}
|
|
98501
|
+
async function runInlineRunDecide(input) {
|
|
98502
|
+
const decideFailed = (error51) => {
|
|
98503
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error: error51 })}
|
|
98504
|
+
`);
|
|
98505
|
+
return 1;
|
|
98506
|
+
};
|
|
98507
|
+
let outcome;
|
|
98508
|
+
try {
|
|
98509
|
+
outcome = await runDecideForInlineRun({
|
|
98510
|
+
workdir: process.cwd(),
|
|
98511
|
+
optionsFile: input.optionsFile,
|
|
98512
|
+
enabled: input.enabled
|
|
98513
|
+
});
|
|
98514
|
+
} catch (error51) {
|
|
98515
|
+
return decideFailed(error51 instanceof Error ? error51.message : String(error51));
|
|
98516
|
+
}
|
|
98517
|
+
if (!outcome.ok)
|
|
98518
|
+
return decideFailed(outcome.error ?? "decide failed without an error message");
|
|
98519
|
+
process.stdout.write(`${JSON.stringify({ runId: input.runId, node: input.node, ...outcome, ok: true })}
|
|
98520
|
+
`);
|
|
98521
|
+
appendInlineRunLogLine(input.runId, `decide node=${input.node} value=${outcome.value ?? ""} source=${outcome.source ?? "default"} reason=${outcome.reason ?? ""}`);
|
|
98522
|
+
return runInlineRunTrace({
|
|
98523
|
+
runId: input.runId,
|
|
98524
|
+
close: false,
|
|
98525
|
+
node: input.node,
|
|
98526
|
+
kind: "decide",
|
|
98527
|
+
status: "done",
|
|
98528
|
+
ok: true,
|
|
98529
|
+
durationMs: outcome.durationMs ?? 0
|
|
98530
|
+
});
|
|
98531
|
+
}
|
|
98532
|
+
async function runInlineRunPersistOut(input) {
|
|
98533
|
+
try {
|
|
98534
|
+
const result = await persistWorktreeRuns({
|
|
98535
|
+
fromWorkdir: input.from,
|
|
98536
|
+
toWorkdir: process.cwd(),
|
|
98537
|
+
...input.taskFiles.length > 0 ? { taskFiles: input.taskFiles } : {}
|
|
98538
|
+
});
|
|
98539
|
+
process.stdout.write(`${JSON.stringify({ ok: true, persisted: result.persisted, skipped: result.skipped })}
|
|
98540
|
+
`);
|
|
98541
|
+
return 0;
|
|
98542
|
+
} catch (error51) {
|
|
98543
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
98544
|
+
process.stdout.write(`${JSON.stringify({ ok: false, error: message })}
|
|
98545
|
+
`);
|
|
98546
|
+
return 1;
|
|
98547
|
+
}
|
|
98548
|
+
}
|
|
98549
|
+
async function runInlineRunSetup(input) {
|
|
98550
|
+
const workdir = process.cwd();
|
|
98551
|
+
const projectDb = await openInlineRunProjectDb(workdir);
|
|
98552
|
+
let exitCode = 0;
|
|
98553
|
+
try {
|
|
98554
|
+
const result = await createOrAttachInlineRun({
|
|
98555
|
+
workdir,
|
|
98556
|
+
getDb: async () => projectDb.adapter,
|
|
98557
|
+
file: input.file,
|
|
98558
|
+
runId: input.runId,
|
|
98559
|
+
inventory: input.inventory,
|
|
98560
|
+
...input.embeddedSchemas !== undefined ? { embeddedSchemas: input.embeddedSchemas } : {}
|
|
98561
|
+
});
|
|
98562
|
+
writeInlineRunOutcome(input.runId, result);
|
|
98563
|
+
if (!result.ok) {
|
|
98564
|
+
console.error(`inline-run-setup: FAIL for run ${input.runId}`);
|
|
98565
|
+
console.error(` ${result.error}`);
|
|
98566
|
+
exitCode = 1;
|
|
98567
|
+
} else {
|
|
98568
|
+
console.error(`inline-run-setup: ${result.attached ? "attached" : "created"} run ${input.runId} (${result.workflowName}, layer ${result.layer}, digest ${result.definitionDigest}, status ${result.status})`);
|
|
98569
|
+
}
|
|
98570
|
+
} finally {
|
|
98571
|
+
projectDb.close();
|
|
98572
|
+
}
|
|
98573
|
+
return exitCode;
|
|
98574
|
+
}
|
|
98575
|
+
var appendFileSync5, closeSync4, existsSync8, mkdirSync8, openSync4, readFileSync12, renameSync6, unlinkSync4, writeFileSync7, writeSync3, lstat2, readdir2, readFile3, SAFE_RUN_ID_RE, MAX_CITED_RUN_FILES = 64, RUN_CITATION_RE, CLOSE_STATUSES, ACTION_STATUSES;
|
|
97492
98576
|
var init_inline_run_setup = __esm(async () => {
|
|
97493
98577
|
init_src2();
|
|
97494
98578
|
init_dist8();
|
|
97495
98579
|
init_dist5();
|
|
98580
|
+
init_action_trace();
|
|
97496
98581
|
init_decide2();
|
|
98582
|
+
init_proof_input_fingerprint();
|
|
97497
98583
|
init_run_record();
|
|
97498
98584
|
init_terminal_reason();
|
|
97499
98585
|
init_workflow_resolver();
|
|
97500
98586
|
init_workflow_service();
|
|
97501
|
-
({
|
|
97502
|
-
|
|
98587
|
+
({
|
|
98588
|
+
appendFileSync: appendFileSync5,
|
|
98589
|
+
closeSync: closeSync4,
|
|
98590
|
+
existsSync: existsSync8,
|
|
98591
|
+
mkdirSync: mkdirSync8,
|
|
98592
|
+
openSync: openSync4,
|
|
98593
|
+
readFileSync: readFileSync12,
|
|
98594
|
+
renameSync: renameSync6,
|
|
98595
|
+
unlinkSync: unlinkSync4,
|
|
98596
|
+
writeFileSync: writeFileSync7,
|
|
98597
|
+
writeSync: writeSync3
|
|
98598
|
+
} = await import("fs"));
|
|
98599
|
+
({ lstat: lstat2, readdir: readdir2, readFile: readFile3 } = await import("fs/promises"));
|
|
97503
98600
|
SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
97504
98601
|
RUN_CITATION_RE = /(?<![\w~:-]|[\w~:-]\/)\.spur\/run\/([A-Za-z0-9][A-Za-z0-9._*?<>{}|,\u2026-]*)/g;
|
|
98602
|
+
CLOSE_STATUSES = new Set(["done", "failed", "paused"]);
|
|
98603
|
+
ACTION_STATUSES = new Set(["done", "failed"]);
|
|
97505
98604
|
});
|
|
97506
98605
|
|
|
97507
98606
|
// ../../packages/app/src/services/job-worker-service.ts
|
|
@@ -98198,8 +99297,8 @@ var init_process_inventory_service = __esm(() => {
|
|
|
98198
99297
|
});
|
|
98199
99298
|
|
|
98200
99299
|
// ../../packages/app/src/services/project-start.ts
|
|
98201
|
-
import { existsSync as
|
|
98202
|
-
import { basename as
|
|
99300
|
+
import { existsSync as existsSync9 } from "fs";
|
|
99301
|
+
import { basename as basename13, resolve as resolve15 } from "path";
|
|
98203
99302
|
function shQuote(value) {
|
|
98204
99303
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
98205
99304
|
}
|
|
@@ -98244,7 +99343,7 @@ function setDetachedServeSpawnForTests(spawn2) {
|
|
|
98244
99343
|
}
|
|
98245
99344
|
function resolveSpurServeCommand() {
|
|
98246
99345
|
const cliPath = getEnvVar("SPUR_CLI_PATH");
|
|
98247
|
-
if (cliPath !== undefined &&
|
|
99346
|
+
if (cliPath !== undefined && existsSync9(cliPath)) {
|
|
98248
99347
|
return [process.execPath, cliPath];
|
|
98249
99348
|
}
|
|
98250
99349
|
const argv1 = process.argv[1];
|
|
@@ -98255,7 +99354,7 @@ function resolveSpurServeCommand() {
|
|
|
98255
99354
|
}
|
|
98256
99355
|
}
|
|
98257
99356
|
const monorepoCli = resolve15(process.cwd(), "apps/cli/src/index.ts");
|
|
98258
|
-
if (
|
|
99357
|
+
if (existsSync9(monorepoCli)) {
|
|
98259
99358
|
return [process.execPath, monorepoCli];
|
|
98260
99359
|
}
|
|
98261
99360
|
const fromPath = typeof Bun !== "undefined" && typeof Bun.which === "function" ? Bun.which("spur") : null;
|
|
@@ -98268,14 +99367,14 @@ async function startRegisteredProject(registry2, target, options = {}) {
|
|
|
98268
99367
|
let entry = await registry2.getByName(target) ?? await registry2.getByPath(target);
|
|
98269
99368
|
if (!entry) {
|
|
98270
99369
|
const absPath = normalizeProjectPath(target);
|
|
98271
|
-
if (
|
|
98272
|
-
entry = await registry2.upsert({ path: absPath, name:
|
|
99370
|
+
if (existsSync9(absPath)) {
|
|
99371
|
+
entry = await registry2.upsert({ path: absPath, name: basename13(absPath), port: 0 });
|
|
98273
99372
|
} else {
|
|
98274
99373
|
throw new Error(`Project not found in registry: "${target}"`);
|
|
98275
99374
|
}
|
|
98276
99375
|
}
|
|
98277
99376
|
const projectPath = normalizeProjectPath(entry.path);
|
|
98278
|
-
if (!
|
|
99377
|
+
if (!existsSync9(projectPath)) {
|
|
98279
99378
|
throw new Error(`Project path does not exist: "${entry.path}" (resolved to ${projectPath}). Update ~/.config/spur/projects.json.`);
|
|
98280
99379
|
}
|
|
98281
99380
|
if (projectPath !== entry.path) {
|
|
@@ -99313,9 +100412,9 @@ var init_section_matrix_loader = __esm(() => {
|
|
|
99313
100412
|
});
|
|
99314
100413
|
|
|
99315
100414
|
// ../../packages/app/src/services/slash-commands-service.ts
|
|
99316
|
-
import { existsSync as
|
|
100415
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
|
|
99317
100416
|
import { homedir as homedir9 } from "os";
|
|
99318
|
-
import { basename as
|
|
100417
|
+
import { basename as basename14, dirname as dirname20, join as join36, resolve as resolve17 } from "path";
|
|
99319
100418
|
function getBuiltinClaudeCommands() {
|
|
99320
100419
|
return BUILTIN_CLAUDE_COMMANDS.map((cmd) => ({ ...cmd }));
|
|
99321
100420
|
}
|
|
@@ -99337,7 +100436,7 @@ function findPluginsSpCommandsDir(startDir = process.cwd()) {
|
|
|
99337
100436
|
while (true) {
|
|
99338
100437
|
try {
|
|
99339
100438
|
const candidate = join36(current, "plugins", "sp", "commands");
|
|
99340
|
-
if (
|
|
100439
|
+
if (existsSync10(candidate)) {
|
|
99341
100440
|
return candidate;
|
|
99342
100441
|
}
|
|
99343
100442
|
} catch {
|
|
@@ -99356,7 +100455,7 @@ function findProjectClaudeCommandsDir(startDir = process.cwd()) {
|
|
|
99356
100455
|
while (true) {
|
|
99357
100456
|
try {
|
|
99358
100457
|
const candidate = join36(current, ".claude", "commands");
|
|
99359
|
-
if (
|
|
100458
|
+
if (existsSync10(candidate)) {
|
|
99360
100459
|
return candidate;
|
|
99361
100460
|
}
|
|
99362
100461
|
} catch {
|
|
@@ -99390,7 +100489,7 @@ function parseMarkdownFrontmatter(rawContent) {
|
|
|
99390
100489
|
}
|
|
99391
100490
|
}
|
|
99392
100491
|
function scanMarkdownCommands(dirPath, options = {}) {
|
|
99393
|
-
if (!
|
|
100492
|
+
if (!existsSync10(dirPath)) {
|
|
99394
100493
|
return [];
|
|
99395
100494
|
}
|
|
99396
100495
|
const { prefix = "/", source = "plugin:sp", defaultCategory, pluginId } = options;
|
|
@@ -99402,8 +100501,8 @@ function scanMarkdownCommands(dirPath, options = {}) {
|
|
|
99402
100501
|
continue;
|
|
99403
100502
|
}
|
|
99404
100503
|
const fullPath = join36(dirPath, entry.name);
|
|
99405
|
-
const stem =
|
|
99406
|
-
const fileContent =
|
|
100504
|
+
const stem = basename14(entry.name, ".md");
|
|
100505
|
+
const fileContent = readFileSync13(fullPath, "utf-8");
|
|
99407
100506
|
const { frontmatter, body } = parseMarkdownFrontmatter(fileContent);
|
|
99408
100507
|
const cleanPrefix = prefix.endsWith(":") ? prefix : prefix.endsWith("/") ? prefix : `${prefix}:`;
|
|
99409
100508
|
const name = prefix === "/" ? `/${stem}` : `${cleanPrefix}${stem}`;
|
|
@@ -99493,15 +100592,15 @@ function discoverEnabledClaudePlugins(options = {}) {
|
|
|
99493
100592
|
const projectSettingsPath = join36(projectRoot, ".claude", "settings.json");
|
|
99494
100593
|
const installedPath = join36(claudeDir, "plugins", "installed_plugins.json");
|
|
99495
100594
|
let userSettings = {};
|
|
99496
|
-
if (
|
|
100595
|
+
if (existsSync10(userSettingsPath)) {
|
|
99497
100596
|
try {
|
|
99498
|
-
userSettings = JSON.parse(
|
|
100597
|
+
userSettings = JSON.parse(readFileSync13(userSettingsPath, "utf-8"));
|
|
99499
100598
|
} catch {}
|
|
99500
100599
|
}
|
|
99501
100600
|
let projectSettings = {};
|
|
99502
|
-
if (
|
|
100601
|
+
if (existsSync10(projectSettingsPath)) {
|
|
99503
100602
|
try {
|
|
99504
|
-
projectSettings = JSON.parse(
|
|
100603
|
+
projectSettings = JSON.parse(readFileSync13(projectSettingsPath, "utf-8"));
|
|
99505
100604
|
} catch {}
|
|
99506
100605
|
}
|
|
99507
100606
|
const enabledPlugins = {
|
|
@@ -99513,9 +100612,9 @@ function discoverEnabledClaudePlugins(options = {}) {
|
|
|
99513
100612
|
...projectSettings.extraKnownMarketplaces ?? {}
|
|
99514
100613
|
};
|
|
99515
100614
|
let installedManifest = { plugins: {} };
|
|
99516
|
-
if (
|
|
100615
|
+
if (existsSync10(installedPath)) {
|
|
99517
100616
|
try {
|
|
99518
|
-
installedManifest = JSON.parse(
|
|
100617
|
+
installedManifest = JSON.parse(readFileSync13(installedPath, "utf-8"));
|
|
99519
100618
|
} catch {}
|
|
99520
100619
|
}
|
|
99521
100620
|
const installedPlugins = installedManifest.plugins ?? {};
|
|
@@ -99544,7 +100643,7 @@ function discoverEnabledClaudePlugins(options = {}) {
|
|
|
99544
100643
|
const installs = installedPlugins[pluginKey];
|
|
99545
100644
|
if (Array.isArray(installs)) {
|
|
99546
100645
|
for (const inst of installs) {
|
|
99547
|
-
if (typeof inst.installPath === "string" &&
|
|
100646
|
+
if (typeof inst.installPath === "string" && existsSync10(inst.installPath)) {
|
|
99548
100647
|
resolvedDir = inst.installPath;
|
|
99549
100648
|
break;
|
|
99550
100649
|
}
|
|
@@ -99555,16 +100654,16 @@ function discoverEnabledClaudePlugins(options = {}) {
|
|
|
99555
100654
|
if (mSrc?.source === "directory" && typeof mSrc.path === "string") {
|
|
99556
100655
|
const cand1 = join36(mSrc.path, "plugins", pluginName);
|
|
99557
100656
|
const cand2 = join36(mSrc.path, pluginName);
|
|
99558
|
-
if (
|
|
100657
|
+
if (existsSync10(cand1)) {
|
|
99559
100658
|
resolvedDir = cand1;
|
|
99560
|
-
} else if (
|
|
100659
|
+
} else if (existsSync10(cand2)) {
|
|
99561
100660
|
resolvedDir = cand2;
|
|
99562
100661
|
}
|
|
99563
100662
|
}
|
|
99564
100663
|
}
|
|
99565
100664
|
if (!resolvedDir && marketplaceName) {
|
|
99566
100665
|
const cacheDir = join36(claudeDir, "plugins", "cache", marketplaceName, pluginName);
|
|
99567
|
-
if (
|
|
100666
|
+
if (existsSync10(cacheDir)) {
|
|
99568
100667
|
try {
|
|
99569
100668
|
const entries = readdirSync7(cacheDir, { withFileTypes: true });
|
|
99570
100669
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -99577,20 +100676,20 @@ function discoverEnabledClaudePlugins(options = {}) {
|
|
|
99577
100676
|
}
|
|
99578
100677
|
if (!resolvedDir && marketplaceName) {
|
|
99579
100678
|
const mpDir = join36(claudeDir, "plugins", "marketplaces", marketplaceName, "plugins", pluginName);
|
|
99580
|
-
if (
|
|
100679
|
+
if (existsSync10(mpDir)) {
|
|
99581
100680
|
resolvedDir = mpDir;
|
|
99582
100681
|
}
|
|
99583
100682
|
}
|
|
99584
100683
|
if (!resolvedDir) {
|
|
99585
100684
|
const localPlugin = resolve17(projectRoot, "plugins", pluginName);
|
|
99586
|
-
if (
|
|
100685
|
+
if (existsSync10(localPlugin)) {
|
|
99587
100686
|
resolvedDir = localPlugin;
|
|
99588
100687
|
}
|
|
99589
100688
|
}
|
|
99590
100689
|
let commandsDir;
|
|
99591
100690
|
if (resolvedDir) {
|
|
99592
100691
|
const candidateCommands = join36(resolvedDir, "commands");
|
|
99593
|
-
if (
|
|
100692
|
+
if (existsSync10(candidateCommands)) {
|
|
99594
100693
|
commandsDir = candidateCommands;
|
|
99595
100694
|
}
|
|
99596
100695
|
}
|
|
@@ -99639,7 +100738,7 @@ function generateSlashCommands(options = {}) {
|
|
|
99639
100738
|
}
|
|
99640
100739
|
if (includePluginSp) {
|
|
99641
100740
|
const spCommandsDir = findPluginsSpCommandsDir(projectRoot) ?? resolve17(projectRoot, "plugins", "sp", "commands");
|
|
99642
|
-
if (
|
|
100741
|
+
if (existsSync10(spCommandsDir)) {
|
|
99643
100742
|
const spCommands = scanMarkdownCommands(spCommandsDir, {
|
|
99644
100743
|
prefix: "/sp:",
|
|
99645
100744
|
source: "plugin:sp",
|
|
@@ -99694,7 +100793,7 @@ function generateSlashCommandsFile(targetPath = getSlashCommandsFilePath(), opti
|
|
|
99694
100793
|
};
|
|
99695
100794
|
try {
|
|
99696
100795
|
const dir = dirname20(targetPath);
|
|
99697
|
-
if (!
|
|
100796
|
+
if (!existsSync10(dir)) {
|
|
99698
100797
|
mkdirSync9(dir, { recursive: true });
|
|
99699
100798
|
}
|
|
99700
100799
|
writeFileSync8(targetPath, JSON.stringify(filePayload, null, 2), "utf-8");
|
|
@@ -99706,7 +100805,7 @@ function generateSlashCommandsFile(targetPath = getSlashCommandsFilePath(), opti
|
|
|
99706
100805
|
};
|
|
99707
100806
|
}
|
|
99708
100807
|
function loadSlashCommands(filePath = getSlashCommandsFilePath(), autoGenerateIfMissing = true, options = {}) {
|
|
99709
|
-
if (!
|
|
100808
|
+
if (!existsSync10(filePath)) {
|
|
99710
100809
|
if (autoGenerateIfMissing) {
|
|
99711
100810
|
const generated = generateSlashCommandsFile(filePath, options);
|
|
99712
100811
|
return generated.commands;
|
|
@@ -99714,7 +100813,7 @@ function loadSlashCommands(filePath = getSlashCommandsFilePath(), autoGenerateIf
|
|
|
99714
100813
|
return getBuiltinClaudeCommands();
|
|
99715
100814
|
}
|
|
99716
100815
|
try {
|
|
99717
|
-
const content =
|
|
100816
|
+
const content = readFileSync13(filePath, "utf-8");
|
|
99718
100817
|
const raw = JSON.parse(content);
|
|
99719
100818
|
const parsed = slashCommandsFileSchema.safeParse(raw);
|
|
99720
100819
|
if (parsed.success && parsed.data.commands.length > 0) {
|
|
@@ -100763,7 +101862,7 @@ var init_system_event_emitter = __esm(() => {
|
|
|
100763
101862
|
});
|
|
100764
101863
|
|
|
100765
101864
|
// ../../packages/app/src/services/task-readiness.ts
|
|
100766
|
-
import { createHash as
|
|
101865
|
+
import { createHash as createHash12 } from "crypto";
|
|
100767
101866
|
function readyRefineCommand(wbs) {
|
|
100768
101867
|
return `/sp:dev-refine ${wbs} --auto --depth ready`;
|
|
100769
101868
|
}
|
|
@@ -100951,7 +102050,7 @@ function computePlanningDigest(raw) {
|
|
|
100951
102050
|
featureId: typeof fm.feature_id === "string" ? fm.feature_id : null,
|
|
100952
102051
|
template: typeof fm.template === "string" ? fm.template : null
|
|
100953
102052
|
};
|
|
100954
|
-
return
|
|
102053
|
+
return createHash12("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
100955
102054
|
}
|
|
100956
102055
|
function verifyReadyChecks(checks4) {
|
|
100957
102056
|
if (checks4 === undefined || checks4.length === 0) {
|
|
@@ -101100,56 +102199,6 @@ var init_task_scaffold = __esm(() => {
|
|
|
101100
102199
|
init_task_locator();
|
|
101101
102200
|
});
|
|
101102
102201
|
|
|
101103
|
-
// ../../packages/app/src/services/task-size-precheck.ts
|
|
101104
|
-
function sectionBody(content, headingPattern) {
|
|
101105
|
-
const heading = content.match(new RegExp(headingPattern, "m"));
|
|
101106
|
-
if (!heading)
|
|
101107
|
-
return null;
|
|
101108
|
-
const rest = content.slice((heading.index ?? 0) + heading[0].length);
|
|
101109
|
-
const nextHeading = rest.match(/^#{2,3}\s+/m);
|
|
101110
|
-
return nextHeading ? rest.slice(0, nextHeading.index ?? 0) : rest;
|
|
101111
|
-
}
|
|
101112
|
-
function countRItems(content) {
|
|
101113
|
-
const body = sectionBody(content, "^#{2,3}\\s+Requirements\\s*$") ?? content;
|
|
101114
|
-
const numbers = new Set;
|
|
101115
|
-
for (const match of body.matchAll(new RegExp(R_ITEM_RE_SOURCE, "gm"))) {
|
|
101116
|
-
numbers.add(match[1] ?? "");
|
|
101117
|
-
}
|
|
101118
|
-
return numbers.size;
|
|
101119
|
-
}
|
|
101120
|
-
function countPlanItems(content) {
|
|
101121
|
-
const body = sectionBody(content, "^#{2,3}\\s+Plan\\s*$");
|
|
101122
|
-
if (body === null)
|
|
101123
|
-
return 0;
|
|
101124
|
-
const numbered = body.match(new RegExp(NUMBERED_PLAN_ITEM_RE_SOURCE, "gm"))?.length ?? 0;
|
|
101125
|
-
const checklist = body.match(new RegExp(CHECKLIST_ITEM_RE_SOURCE, "gm"))?.length ?? 0;
|
|
101126
|
-
return numbered + checklist;
|
|
101127
|
-
}
|
|
101128
|
-
function evaluateTaskSize(content, limits = DEFAULT_TASK_SIZE_LIMITS) {
|
|
101129
|
-
const reqCount = countRItems(content);
|
|
101130
|
-
const planItemCount = countPlanItems(content);
|
|
101131
|
-
const reasons = [];
|
|
101132
|
-
if (reqCount > limits.maxReqs) {
|
|
101133
|
-
reasons.push(`Task has ${reqCount} R-items (max ${limits.maxReqs}). ` + `Consider decomposing into smaller tasks or raise maxImplementReqs via --vars.`);
|
|
101134
|
-
}
|
|
101135
|
-
if (planItemCount > limits.maxPlanItems) {
|
|
101136
|
-
reasons.push(`Task has ${planItemCount} Plan items (max ${limits.maxPlanItems}). ` + `Consider simplifying the plan or raise maxImplementPlanItems via --vars.`);
|
|
101137
|
-
}
|
|
101138
|
-
return {
|
|
101139
|
-
reqCount,
|
|
101140
|
-
planItemCount,
|
|
101141
|
-
ok: reasons.length === 0,
|
|
101142
|
-
reasons
|
|
101143
|
-
};
|
|
101144
|
-
}
|
|
101145
|
-
var DEFAULT_TASK_SIZE_LIMITS, R_ITEM_RE_SOURCE = "^\\s*[-*]\\s+(?:\\[[ xX]\\]\\s*)?[*_]{0,2}R(\\d+)\\.?[*_]{0,2}(?:\\s|$)", NUMBERED_PLAN_ITEM_RE_SOURCE = "^\\d+\\.\\s", CHECKLIST_ITEM_RE_SOURCE = "^\\s*[-*]\\s+\\[[ xX]\\]";
|
|
101146
|
-
var init_task_size_precheck = __esm(() => {
|
|
101147
|
-
DEFAULT_TASK_SIZE_LIMITS = {
|
|
101148
|
-
maxReqs: 10,
|
|
101149
|
-
maxPlanItems: 16
|
|
101150
|
-
};
|
|
101151
|
-
});
|
|
101152
|
-
|
|
101153
102202
|
// ../../packages/app/src/services/task-transition.ts
|
|
101154
102203
|
async function runTransitionCheckGate(gate, wbs, filePath, target) {
|
|
101155
102204
|
const result = await gate.service.check(filePath, wbs, {
|
|
@@ -101831,11 +102880,13 @@ ${templateBackground}`;
|
|
|
101831
102880
|
await this.writeService.updateSection(ref, "Solution", solutionBody);
|
|
101832
102881
|
result.solutionBackfilled = true;
|
|
101833
102882
|
}
|
|
102883
|
+
const featureAc = await this.resolveFeatureAcBody(filePath).catch(() => null);
|
|
102884
|
+
const scenarioTitles = featureScenarioTitles(featureAc?.ac ?? "");
|
|
101834
102885
|
for (const section of ["Requirements", "Acceptance Criteria"]) {
|
|
101835
102886
|
const body = doc2.getSection(section);
|
|
101836
102887
|
if (body === null || body.trim().length === 0)
|
|
101837
102888
|
continue;
|
|
101838
|
-
const flipped = flipVerifiedCheckboxes(body, verdict);
|
|
102889
|
+
const flipped = flipVerifiedCheckboxes(body, verdict, scenarioTitles);
|
|
101839
102890
|
if (flipped !== body) {
|
|
101840
102891
|
await this.writeService.updateSection(ref, section, flipped);
|
|
101841
102892
|
}
|
|
@@ -102087,8 +103138,8 @@ ${block}` : block);
|
|
|
102087
103138
|
return { wbs: hit.wbs, filePath: hit.filePath };
|
|
102088
103139
|
if (opts.strict === true)
|
|
102089
103140
|
return null;
|
|
102090
|
-
const
|
|
102091
|
-
const capturedWbs = /^(\d{4})_.+\.md$/.exec(
|
|
103141
|
+
const basename15 = filePath.split("/").pop() ?? "";
|
|
103142
|
+
const capturedWbs = /^(\d{4})_.+\.md$/.exec(basename15)?.[1];
|
|
102092
103143
|
if (capturedWbs) {
|
|
102093
103144
|
const taskPath = await this.locator.findPathByWbs(capturedWbs);
|
|
102094
103145
|
if (taskPath !== null)
|
|
@@ -102305,7 +103356,7 @@ function extractRequirements(text4) {
|
|
|
102305
103356
|
}
|
|
102306
103357
|
if (!trimmed.startsWith("|"))
|
|
102307
103358
|
continue;
|
|
102308
|
-
const cells =
|
|
103359
|
+
const cells = splitTableCells2(trimmed);
|
|
102309
103360
|
if (!inTable && cells.length >= 2) {
|
|
102310
103361
|
const h0 = (cells[0] ?? "").toLowerCase().trim();
|
|
102311
103362
|
const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
@@ -102356,7 +103407,7 @@ function normalizeStatus(raw) {
|
|
|
102356
103407
|
return "UNMET";
|
|
102357
103408
|
return null;
|
|
102358
103409
|
}
|
|
102359
|
-
function
|
|
103410
|
+
function splitTableCells2(row) {
|
|
102360
103411
|
return row.split(/(?<!\\)\|/).map((c3) => c3.replace(/\\\|/g, "|").trim()).filter(Boolean);
|
|
102361
103412
|
}
|
|
102362
103413
|
function extractAcceptanceCriteria(text4) {
|
|
@@ -102373,7 +103424,7 @@ function extractAcceptanceCriteria(text4) {
|
|
|
102373
103424
|
}
|
|
102374
103425
|
if (!trimmed.startsWith("|"))
|
|
102375
103426
|
continue;
|
|
102376
|
-
const cells =
|
|
103427
|
+
const cells = splitTableCells2(trimmed);
|
|
102377
103428
|
if (!inTable && cells.length >= 4) {
|
|
102378
103429
|
const h0 = (cells[0] ?? "").toLowerCase();
|
|
102379
103430
|
const h1 = (cells[1] ?? "").toLowerCase();
|
|
@@ -102390,7 +103441,7 @@ function extractAcceptanceCriteria(text4) {
|
|
|
102390
103441
|
const statusRaw = cells[1] ?? "";
|
|
102391
103442
|
const evidenceTypeRaw = cells[2] ?? "";
|
|
102392
103443
|
const status = normalizeAcceptanceCriteriaStatus(statusRaw);
|
|
102393
|
-
const evidenceType =
|
|
103444
|
+
const evidenceType = normalizeEvidenceType2(evidenceTypeRaw);
|
|
102394
103445
|
const acShaped = cells.length >= 4 || id.length > 0 && status !== null;
|
|
102395
103446
|
const evidence = cells[3] ?? "";
|
|
102396
103447
|
if (acShaped && status !== null && evidenceType !== null && id.length > 0) {
|
|
@@ -102414,7 +103465,7 @@ function normalizeAcceptanceCriteriaStatus(raw) {
|
|
|
102414
103465
|
return "N/A";
|
|
102415
103466
|
return null;
|
|
102416
103467
|
}
|
|
102417
|
-
function
|
|
103468
|
+
function normalizeEvidenceTypeToken2(normalized) {
|
|
102418
103469
|
if (normalized === "test")
|
|
102419
103470
|
return "test";
|
|
102420
103471
|
if (normalized === "command")
|
|
@@ -102430,16 +103481,16 @@ function normalizeEvidenceTypeToken(normalized) {
|
|
|
102430
103481
|
return "n/a";
|
|
102431
103482
|
return null;
|
|
102432
103483
|
}
|
|
102433
|
-
function
|
|
103484
|
+
function normalizeEvidenceType2(raw) {
|
|
102434
103485
|
const normalized = raw.toLowerCase().trim();
|
|
102435
|
-
const single =
|
|
103486
|
+
const single = normalizeEvidenceTypeToken2(normalized);
|
|
102436
103487
|
if (single !== null)
|
|
102437
103488
|
return single;
|
|
102438
|
-
const tokens = normalized.split(/[+,/]/).map((part) =>
|
|
103489
|
+
const tokens = normalized.split(/[+,/]/).map((part) => normalizeEvidenceTypeToken2(part.trim())).filter((token) => token !== null);
|
|
102439
103490
|
if (tokens.length < 2 || tokens.length !== normalized.split(/[+,/]/).filter((p) => p.trim()).length) {
|
|
102440
103491
|
return null;
|
|
102441
103492
|
}
|
|
102442
|
-
return
|
|
103493
|
+
return EVIDENCE_TYPE_PRECEDENCE2.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
102443
103494
|
}
|
|
102444
103495
|
function applyAcceptanceCriteriaEvidenceRule(rows) {
|
|
102445
103496
|
return rows.map((row) => {
|
|
@@ -102470,7 +103521,7 @@ function extractChecks(_text, taskCheckPassed, requirements, acceptanceCriteria,
|
|
|
102470
103521
|
const trimmed = line.trim();
|
|
102471
103522
|
if (!trimmed.startsWith("|"))
|
|
102472
103523
|
continue;
|
|
102473
|
-
const cells =
|
|
103524
|
+
const cells = splitTableCells2(trimmed);
|
|
102474
103525
|
if (cells.length >= 2) {
|
|
102475
103526
|
const h0 = (cells[0] ?? "").toLowerCase();
|
|
102476
103527
|
const h1 = (cells[1] ?? "").toLowerCase();
|
|
@@ -102548,10 +103599,10 @@ function aggregateBatchVerdicts(results) {
|
|
|
102548
103599
|
const summary = `${counts.PASS} PASS, ${counts.PARTIAL} PARTIAL, ${counts.FAIL} FAIL, ${notStarted.length} NOT-STARTED (excluded)`;
|
|
102549
103600
|
return { verdict, rolledUp, notStarted, summary };
|
|
102550
103601
|
}
|
|
102551
|
-
var
|
|
103602
|
+
var EVIDENCE_TYPE_PRECEDENCE2, NOT_STARTED_STATUSES;
|
|
102552
103603
|
var init_task_verdict = __esm(() => {
|
|
102553
103604
|
init_verify_verdict();
|
|
102554
|
-
|
|
103605
|
+
EVIDENCE_TYPE_PRECEDENCE2 = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
102555
103606
|
NOT_STARTED_STATUSES = {
|
|
102556
103607
|
backlog: true,
|
|
102557
103608
|
todo: true,
|
|
@@ -102564,7 +103615,7 @@ var init_task_verdict = __esm(() => {
|
|
|
102564
103615
|
});
|
|
102565
103616
|
|
|
102566
103617
|
// ../../packages/app/src/services/token-ledger-service.ts
|
|
102567
|
-
import { closeSync as
|
|
103618
|
+
import { closeSync as closeSync5, existsSync as existsSync11, fstatSync as fstatSync2, openSync as openSync5, readSync } from "fs";
|
|
102568
103619
|
import { join as join38 } from "path";
|
|
102569
103620
|
function isSparseToolActivity(events2) {
|
|
102570
103621
|
return events2.length === 0 || !events2.some((e) => TOOL_ACTIVITY_TYPES.has(e.type));
|
|
@@ -102616,11 +103667,11 @@ function parseLedgerLine(line) {
|
|
|
102616
103667
|
}
|
|
102617
103668
|
}
|
|
102618
103669
|
function tailTokenLedgerFile(filePath, limit, chunkSize = DEFAULT_CHUNK, before) {
|
|
102619
|
-
if (!
|
|
103670
|
+
if (!existsSync11(filePath)) {
|
|
102620
103671
|
return { events: [], truncated: false };
|
|
102621
103672
|
}
|
|
102622
103673
|
const target = limit + 1;
|
|
102623
|
-
const fd =
|
|
103674
|
+
const fd = openSync5(filePath, "r");
|
|
102624
103675
|
try {
|
|
102625
103676
|
const { size } = fstatSync2(fd);
|
|
102626
103677
|
if (size === 0)
|
|
@@ -102658,7 +103709,7 @@ function tailTokenLedgerFile(filePath, limit, chunkSize = DEFAULT_CHUNK, before)
|
|
|
102658
103709
|
truncated
|
|
102659
103710
|
};
|
|
102660
103711
|
} finally {
|
|
102661
|
-
|
|
103712
|
+
closeSync5(fd);
|
|
102662
103713
|
}
|
|
102663
103714
|
}
|
|
102664
103715
|
|
|
@@ -102712,7 +103763,7 @@ var init_token_ledger_service = __esm(() => {
|
|
|
102712
103763
|
});
|
|
102713
103764
|
|
|
102714
103765
|
// ../../packages/app/src/services/token-ledger-watcher.ts
|
|
102715
|
-
import { closeSync as
|
|
103766
|
+
import { closeSync as closeSync6, existsSync as existsSync12, fstatSync as fstatSync3, openSync as openSync6, readSync as readSync2, watch } from "fs";
|
|
102716
103767
|
|
|
102717
103768
|
class TokenLedgerWatcher {
|
|
102718
103769
|
ledgerPath;
|
|
@@ -102733,7 +103784,7 @@ class TokenLedgerWatcher {
|
|
|
102733
103784
|
this.started = true;
|
|
102734
103785
|
this.offset = this.currentSize();
|
|
102735
103786
|
try {
|
|
102736
|
-
const target =
|
|
103787
|
+
const target = existsSync12(this.ledgerPath) ? this.ledgerPath : this.parentDir();
|
|
102737
103788
|
this.watcher = watch(target, () => this.schedulePoll());
|
|
102738
103789
|
} catch {
|
|
102739
103790
|
this.watcher = undefined;
|
|
@@ -102768,14 +103819,14 @@ class TokenLedgerWatcher {
|
|
|
102768
103819
|
return idx > 0 ? this.ledgerPath.slice(0, idx) : ".";
|
|
102769
103820
|
}
|
|
102770
103821
|
currentSize() {
|
|
102771
|
-
if (!
|
|
103822
|
+
if (!existsSync12(this.ledgerPath))
|
|
102772
103823
|
return 0;
|
|
102773
103824
|
try {
|
|
102774
|
-
const fd =
|
|
103825
|
+
const fd = openSync6(this.ledgerPath, "r");
|
|
102775
103826
|
try {
|
|
102776
103827
|
return fstatSync3(fd).size;
|
|
102777
103828
|
} finally {
|
|
102778
|
-
|
|
103829
|
+
closeSync6(fd);
|
|
102779
103830
|
}
|
|
102780
103831
|
} catch {
|
|
102781
103832
|
return 0;
|
|
@@ -102790,14 +103841,14 @@ class TokenLedgerWatcher {
|
|
|
102790
103841
|
}, this.debounceMs);
|
|
102791
103842
|
}
|
|
102792
103843
|
pollNewBytes() {
|
|
102793
|
-
if (!
|
|
103844
|
+
if (!existsSync12(this.ledgerPath)) {
|
|
102794
103845
|
this.offset = 0;
|
|
102795
103846
|
this.carry = "";
|
|
102796
103847
|
return;
|
|
102797
103848
|
}
|
|
102798
103849
|
let size;
|
|
102799
103850
|
try {
|
|
102800
|
-
const fd =
|
|
103851
|
+
const fd = openSync6(this.ledgerPath, "r");
|
|
102801
103852
|
try {
|
|
102802
103853
|
size = fstatSync3(fd).size;
|
|
102803
103854
|
if (size < this.offset) {
|
|
@@ -102825,7 +103876,7 @@ class TokenLedgerWatcher {
|
|
|
102825
103876
|
}
|
|
102826
103877
|
}
|
|
102827
103878
|
} finally {
|
|
102828
|
-
|
|
103879
|
+
closeSync6(fd);
|
|
102829
103880
|
}
|
|
102830
103881
|
} catch {}
|
|
102831
103882
|
}
|
|
@@ -103277,7 +104328,10 @@ class LifecycleAdapter {
|
|
|
103277
104328
|
});
|
|
103278
104329
|
}
|
|
103279
104330
|
await svc.reseedRun(workflow, runId, currentStatus);
|
|
103280
|
-
const result = await svc.requestTransition(workflow, runId, to, {
|
|
104331
|
+
const result = await svc.requestTransition(workflow, runId, to, {
|
|
104332
|
+
workdir: this.opts.cwd,
|
|
104333
|
+
vars: { [profile.varKey]: ref.id, spurBin: this.opts.spurBin }
|
|
104334
|
+
});
|
|
103281
104335
|
if (result.allowed) {
|
|
103282
104336
|
const enterFailure = await this.runTargetEnterActions(workflow, to, runId);
|
|
103283
104337
|
if (enterFailure !== null) {
|
|
@@ -103768,6 +104822,7 @@ var init_trace_writer = __esm(() => {
|
|
|
103768
104822
|
var exports_src2 = {};
|
|
103769
104823
|
__export(exports_src2, {
|
|
103770
104824
|
writeJsonError: () => writeJsonError,
|
|
104825
|
+
writeInlineRunOutcome: () => writeInlineRunOutcome,
|
|
103771
104826
|
workflowLayers: () => workflowLayers,
|
|
103772
104827
|
withActionTrace: () => withActionTrace,
|
|
103773
104828
|
waitForOccupant: () => waitForOccupant,
|
|
@@ -103799,6 +104854,12 @@ __export(exports_src2, {
|
|
|
103799
104854
|
schedulerJobTimeoutEnvName: () => schedulerJobTimeoutEnvName,
|
|
103800
104855
|
scanMarkdownCommands: () => scanMarkdownCommands,
|
|
103801
104856
|
satisfies: () => satisfies,
|
|
104857
|
+
runInlineRunTraceBatch: () => runInlineRunTraceBatch,
|
|
104858
|
+
runInlineRunTrace: () => runInlineRunTrace,
|
|
104859
|
+
runInlineRunSetup: () => runInlineRunSetup,
|
|
104860
|
+
runInlineRunPersistOut: () => runInlineRunPersistOut,
|
|
104861
|
+
runInlineRunFingerprint: () => runInlineRunFingerprint,
|
|
104862
|
+
runInlineRunDecide: () => runInlineRunDecide,
|
|
103802
104863
|
runIdeaHandoffCli: () => runIdeaHandoffCli,
|
|
103803
104864
|
runHistoryReport: () => runHistoryReport,
|
|
103804
104865
|
runDecideForInlineRun: () => runDecideForInlineRun,
|
|
@@ -103850,6 +104911,7 @@ __export(exports_src2, {
|
|
|
103850
104911
|
readVerdictArtifact: () => readVerdictArtifact,
|
|
103851
104912
|
readVerdict: () => readVerdict,
|
|
103852
104913
|
readProofInputContents: () => readProofInputContents,
|
|
104914
|
+
readFeatureBody: () => readFeatureBody,
|
|
103853
104915
|
qualifySectionBody: () => qualifySectionBody,
|
|
103854
104916
|
qualifyAnchors: () => qualifyAnchors,
|
|
103855
104917
|
pruneReports: () => pruneReports,
|
|
@@ -103884,6 +104946,8 @@ __export(exports_src2, {
|
|
|
103884
104946
|
looksLikeOpaqueId: () => looksLikeOpaqueId,
|
|
103885
104947
|
loadSlashCommands: () => loadSlashCommands,
|
|
103886
104948
|
loadSectionMatrix: () => loadSectionMatrix,
|
|
104949
|
+
lintVerifyAnswerForTask: () => lintVerifyAnswerForTask,
|
|
104950
|
+
lintVerifyAnswer: () => lintVerifyAnswer,
|
|
103887
104951
|
isTerminalReason: () => isTerminalReason,
|
|
103888
104952
|
isSystemEventEnvelopeV2: () => isSystemEventEnvelopeV2,
|
|
103889
104953
|
isSchedulerCustomActiveConflict: () => isSchedulerCustomActiveConflict,
|
|
@@ -103892,11 +104956,15 @@ __export(exports_src2, {
|
|
|
103892
104956
|
isPortLive: () => isPortLive,
|
|
103893
104957
|
isPortAvailable: () => isPortAvailable,
|
|
103894
104958
|
isInsideRoot: () => isInsideRoot,
|
|
104959
|
+
isInlineRunCloseStatus: () => isInlineRunCloseStatus,
|
|
104960
|
+
isInlineRunActionStatus: () => isInlineRunActionStatus,
|
|
103895
104961
|
isFindingCode: () => isFindingCode,
|
|
104962
|
+
isFeatureFile: () => isFeatureFile,
|
|
103896
104963
|
isFeatureActionName: () => isFeatureActionName,
|
|
103897
104964
|
isExclusiveJobActive: () => isExclusiveJobActive,
|
|
103898
104965
|
isBookkeepingWorkflow: () => isBookkeepingWorkflow,
|
|
103899
104966
|
installSystemEventCatchAll: () => installSystemEventCatchAll,
|
|
104967
|
+
inlineRunRecordLogPath: () => inlineRunRecordLogPath,
|
|
103900
104968
|
humanWorkflowTitle: () => humanWorkflowTitle,
|
|
103901
104969
|
humanStepLabel: () => humanStepLabel,
|
|
103902
104970
|
hitlConfirmDefault: () => hitlConfirmDefault,
|
|
@@ -103983,6 +105051,7 @@ __export(exports_src2, {
|
|
|
103983
105051
|
assertPiImporterSafe: () => assertPiImporterSafe,
|
|
103984
105052
|
assertInventoryIdentity: () => assertInventoryIdentity,
|
|
103985
105053
|
assertCanonicalTimeoutMs: () => assertCanonicalTimeoutMs,
|
|
105054
|
+
appendInlineRunLogLine: () => appendInlineRunLogLine,
|
|
103986
105055
|
anchorQualify: () => anchorQualify,
|
|
103987
105056
|
aggregateBatchVerdicts: () => aggregateBatchVerdicts,
|
|
103988
105057
|
acquireExclusiveJob: () => acquireExclusiveJob,
|
|
@@ -104129,6 +105198,7 @@ __export(exports_src2, {
|
|
|
104129
105198
|
AgentRunActionRunner: () => AgentRunActionRunner,
|
|
104130
105199
|
AgentExecutionLifecycle: () => AgentExecutionLifecycle,
|
|
104131
105200
|
AgentCoordinationService: () => AgentCoordinationService,
|
|
105201
|
+
ANSWER_LINT_MAX_FINDINGS: () => ANSWER_LINT_MAX_FINDINGS,
|
|
104132
105202
|
ALL_FINDING_CODES: () => ALL_FINDING_CODES,
|
|
104133
105203
|
AGENT_SESSION_SOURCES: () => AGENT_SESSION_SOURCES
|
|
104134
105204
|
});
|
|
@@ -104201,6 +105271,7 @@ var init_src3 = __esm(async () => {
|
|
|
104201
105271
|
init_task_verdict();
|
|
104202
105272
|
init_token_ledger_service();
|
|
104203
105273
|
init_token_ledger_watcher();
|
|
105274
|
+
init_verify_answer_lint();
|
|
104204
105275
|
init_workflow_service();
|
|
104205
105276
|
init_write_slot_service();
|
|
104206
105277
|
init_action_trace();
|
|
@@ -112043,7 +113114,10 @@ var SHARED_OPTIONS = {
|
|
|
112043
113114
|
fileRuleAdhocPath: ["--file <path>", "Ad-hoc rule file path"],
|
|
112044
113115
|
fileTaskBatch: ["--file <path>", "Path to the batch JSON file validated against task-batch.schema.json"],
|
|
112045
113116
|
fileTaskTest: ["--file <path>", "Custom target test file path"],
|
|
112046
|
-
forceFeatureReopen: [
|
|
113117
|
+
forceFeatureReopen: [
|
|
113118
|
+
"--force",
|
|
113119
|
+
"Force applying reopen proposals without confirmation; also bypasses repeated-BLOCKED suppression"
|
|
113120
|
+
],
|
|
112047
113121
|
forceInitRecreate: ["--force", "Recreate files that already exist"],
|
|
112048
113122
|
forceWorkflowClean: ["--force", "Clean ALL non-terminal runs regardless of age (overrides --older-than)"],
|
|
112049
113123
|
forceWorkflowContinue: ["--force", "Proceed with resume even if workflow definition drift is detected"],
|
|
@@ -112059,7 +113133,7 @@ init_loader();
|
|
|
112059
113133
|
init_src2();
|
|
112060
113134
|
init_dist7();
|
|
112061
113135
|
await init_src3();
|
|
112062
|
-
import { existsSync as
|
|
113136
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
112063
113137
|
import { rm, writeFile as writeFile2 } from "fs/promises";
|
|
112064
113138
|
import { tmpdir as tmpdir3 } from "os";
|
|
112065
113139
|
import { dirname as dirname25, join as join41 } from "path";
|
|
@@ -113053,9 +114127,9 @@ init_src2();
|
|
|
113053
114127
|
await init_src3();
|
|
113054
114128
|
|
|
113055
114129
|
// src/workflow/resolve-spur-bin.ts
|
|
113056
|
-
import { basename as
|
|
114130
|
+
import { basename as basename15 } from "path";
|
|
113057
114131
|
function resolveSpurBinFrom(launch) {
|
|
113058
|
-
const runtime =
|
|
114132
|
+
const runtime = basename15(launch.execPath).toLowerCase().replace(/\.exe$/, "");
|
|
113059
114133
|
const isJsRuntime = runtime === "bun" || runtime === "node";
|
|
113060
114134
|
if (isJsRuntime && launch.mainModule) {
|
|
113061
114135
|
return `${launch.execPath} ${launch.mainModule}`;
|
|
@@ -113804,7 +114878,14 @@ Recover with: ${err.recoveryCommand}` : err.message;
|
|
|
113804
114878
|
}
|
|
113805
114879
|
});
|
|
113806
114880
|
task.command("verdict").summary("Derive PASS/PARTIAL/FAIL/UNKNOWN verdict from verify answer text (replaces pipeline grep/shell).").argument("<wbs>", "Task WBS number").option("--from-answer <path>", "Path to verify answer text file").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
|
|
113807
|
-
const {
|
|
114881
|
+
const {
|
|
114882
|
+
ANSWER_LINT_MAX_FINDINGS: ANSWER_LINT_MAX_FINDINGS2,
|
|
114883
|
+
deriveVerdict: deriveVerdict2,
|
|
114884
|
+
lintVerifyAnswerForTask: lintVerifyAnswerForTask2,
|
|
114885
|
+
summarizeRowIds: summarizeRowIds2,
|
|
114886
|
+
VERDICT_SCENARIO_KEY_FORMS: VERDICT_SCENARIO_KEY_FORMS2,
|
|
114887
|
+
verdictScenarioKeyGap: verdictScenarioKeyGap2
|
|
114888
|
+
} = await init_src3().then(() => exports_src2);
|
|
113808
114889
|
const answerPath = options.fromAnswer ?? `.spur/run/${wbs}-verify-answer.txt`;
|
|
113809
114890
|
let answerText;
|
|
113810
114891
|
try {
|
|
@@ -113814,15 +114895,28 @@ Recover with: ${err.recoveryCommand}` : err.message;
|
|
|
113814
114895
|
context4.setExitCode(1);
|
|
113815
114896
|
return;
|
|
113816
114897
|
}
|
|
113817
|
-
const taskCheckPassed = true;
|
|
113818
|
-
const result = deriveVerdict2(answerText, taskCheckPassed);
|
|
113819
114898
|
const { foldersConfig, featuresDir } = await resolvePlanningFolders(context4.fs);
|
|
113820
|
-
const
|
|
114899
|
+
const corpusDeps = {
|
|
113821
114900
|
fs: context4.fs,
|
|
113822
114901
|
tasksDir: context4.fs.resolve(options.folder ?? foldersConfig.active_folder),
|
|
113823
114902
|
foldersConfig,
|
|
113824
114903
|
featuresDir: context4.fs.resolve(featuresDir)
|
|
113825
|
-
}
|
|
114904
|
+
};
|
|
114905
|
+
const lintFindings = await lintVerifyAnswerForTask2(wbs, answerText, corpusDeps);
|
|
114906
|
+
if (lintFindings.length > 0) {
|
|
114907
|
+
if (options.json) {
|
|
114908
|
+
context4.output.write(JSON.stringify({ wbs, lintFindings }, null, 2));
|
|
114909
|
+
} else {
|
|
114910
|
+
context4.output.error(`task verdict: answer lint failed \u2014 ${lintFindings.length}${lintFindings.length >= ANSWER_LINT_MAX_FINDINGS2 ? "+" : ""} finding(s) in ${answerPath}`);
|
|
114911
|
+
for (const f of lintFindings)
|
|
114912
|
+
context4.output.error(` line ${f.line}: ${f.message}`);
|
|
114913
|
+
}
|
|
114914
|
+
context4.setExitCode(1);
|
|
114915
|
+
return;
|
|
114916
|
+
}
|
|
114917
|
+
const taskCheckPassed = true;
|
|
114918
|
+
const result = deriveVerdict2(answerText, taskCheckPassed);
|
|
114919
|
+
const gap = await verdictScenarioKeyGap2(wbs, [...result.requirements, ...result.acceptanceCriteria ?? []], corpusDeps);
|
|
113826
114920
|
if (gap !== null) {
|
|
113827
114921
|
writeJsonError(context4.output, options, `Verdict rows key to no scenario of linked feature ${gap.featureId}: ${summarizeRowIds2(gap.offendingIds)}. Key each row by ${VERDICT_SCENARIO_KEY_FORMS2} (repair: /sp:dev-verify ${wbs})`);
|
|
113828
114922
|
context4.setExitCode(1);
|
|
@@ -113888,9 +114982,14 @@ Recover with: ${err.recoveryCommand}` : err.message;
|
|
|
113888
114982
|
context4.setExitCode(1);
|
|
113889
114983
|
}
|
|
113890
114984
|
});
|
|
113891
|
-
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--corpus", "Explicit unsuppressed audit of active tasks and features; warnings are advisory").option("--since <ref>", "Scope the corpus fog comparison to changes since a git ref (requires --corpus)").option(...SHARED_OPTIONS.strictTaskAll).option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option(...SHARED_OPTIONS.asTaskF92).option("--fix", "repair structural findings in place (heading presence/level/order; adds missing R-item checkbox markers \u2014 flipping verified boxes is task record's job, not --fix)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
|
|
114985
|
+
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--corpus", "Explicit unsuppressed audit of active tasks and features; warnings are advisory").option("--precheck", "Also run the implement-readiness prechecks (size, evidence channel); failures are errors.").option("--since <ref>", "Scope the corpus fog comparison to changes since a git ref (requires --corpus)").option(...SHARED_OPTIONS.strictTaskAll).option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option(...SHARED_OPTIONS.asTaskF92).option("--fix", "repair structural findings in place (heading presence/level/order; adds missing R-item checkbox markers \u2014 flipping verified boxes is task record's job, not --fix)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
|
|
113892
114986
|
const json4 = options.json === true;
|
|
113893
114987
|
const strict = options.strict === true;
|
|
114988
|
+
if (options.precheck === true && (options.corpus || !wbs)) {
|
|
114989
|
+
writeJsonError(context4.output, options, "--precheck requires a <wbs> argument and cannot be combined with --corpus", "VALIDATION_FAILED");
|
|
114990
|
+
context4.setExitCode(2);
|
|
114991
|
+
return;
|
|
114992
|
+
}
|
|
113894
114993
|
if (options.since !== undefined && !options.corpus || options.corpus && (wbs || options.as || options.fix || options.folder || strict || options.strictCore) || options.since?.startsWith("-")) {
|
|
113895
114994
|
writeJsonError(context4.output, options, "--corpus accepts only --since and JSON output options; --since requires --corpus and a git ref", "VALIDATION_FAILED");
|
|
113896
114995
|
context4.setExitCode(2);
|
|
@@ -113957,7 +115056,17 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
113957
115056
|
strict,
|
|
113958
115057
|
asStatus,
|
|
113959
115058
|
severityOverrides: planningFolders.severityOverrides,
|
|
113960
|
-
fix: options.fix === true
|
|
115059
|
+
fix: options.fix === true,
|
|
115060
|
+
...options.precheck === true ? {
|
|
115061
|
+
precheck: true,
|
|
115062
|
+
countArgsRaw: async (source) => {
|
|
115063
|
+
try {
|
|
115064
|
+
return await countToolCallArgsRaw(await context4.getDb(), source);
|
|
115065
|
+
} catch {
|
|
115066
|
+
return null;
|
|
115067
|
+
}
|
|
115068
|
+
}
|
|
115069
|
+
} : {}
|
|
113961
115070
|
});
|
|
113962
115071
|
results.push(result);
|
|
113963
115072
|
printResult(result);
|
|
@@ -114145,14 +115254,14 @@ function loadTemplateBodies(projectRoot, variant) {
|
|
|
114145
115254
|
return cached2;
|
|
114146
115255
|
let bodies = {};
|
|
114147
115256
|
const localPath = join41(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
|
|
114148
|
-
if (
|
|
114149
|
-
bodies = extractTemplateBodies(
|
|
115257
|
+
if (existsSync13(localPath)) {
|
|
115258
|
+
bodies = extractTemplateBodies(readFileSync14(localPath, "utf8"));
|
|
114150
115259
|
} else {
|
|
114151
115260
|
const root = bundledConfigRoot();
|
|
114152
115261
|
if (root !== null) {
|
|
114153
115262
|
const templatePath = join41(root, "templates", "task", `${variant}.md`);
|
|
114154
|
-
if (
|
|
114155
|
-
bodies = extractTemplateBodies(
|
|
115263
|
+
if (existsSync13(templatePath)) {
|
|
115264
|
+
bodies = extractTemplateBodies(readFileSync14(templatePath, "utf8"));
|
|
114156
115265
|
}
|
|
114157
115266
|
}
|
|
114158
115267
|
}
|
|
@@ -114860,12 +115969,12 @@ function waitFail(context4, options, code, message) {
|
|
|
114860
115969
|
// src/release-ops.ts
|
|
114861
115970
|
init_src();
|
|
114862
115971
|
init_dist5();
|
|
114863
|
-
import { existsSync as
|
|
115972
|
+
import { existsSync as existsSync15, readdirSync as readdirSync8, readFileSync as readFileSync15 } from "fs";
|
|
114864
115973
|
import { join as join43 } from "path";
|
|
114865
115974
|
|
|
114866
115975
|
// src/version-carriers.ts
|
|
114867
115976
|
init_zod();
|
|
114868
|
-
import { existsSync as
|
|
115977
|
+
import { existsSync as existsSync14 } from "fs";
|
|
114869
115978
|
import { join as join42 } from "path";
|
|
114870
115979
|
var defaultLiteralProbe = { file: "src/config.ts", identifier: "binaryVersion" };
|
|
114871
115980
|
var registry2 = new Map;
|
|
@@ -114902,7 +116011,7 @@ function findLiteralProbe(carriers, packageDir) {
|
|
|
114902
116011
|
return;
|
|
114903
116012
|
}
|
|
114904
116013
|
async function readJson(path9) {
|
|
114905
|
-
if (!
|
|
116014
|
+
if (!existsSync14(path9))
|
|
114906
116015
|
return null;
|
|
114907
116016
|
try {
|
|
114908
116017
|
return JSON.parse(await Bun.file(path9).text());
|
|
@@ -114977,7 +116086,7 @@ async function run(repoRoot, cmd) {
|
|
|
114977
116086
|
}
|
|
114978
116087
|
async function verifyLockfile(ctx, output2) {
|
|
114979
116088
|
const lockPath = join43(ctx.repoRoot, "bun.lock");
|
|
114980
|
-
if (!
|
|
116089
|
+
if (!existsSync15(lockPath) || Bun.file(lockPath).size === 0)
|
|
114981
116090
|
return;
|
|
114982
116091
|
const result = await run(ctx.repoRoot, ["bun", "install", "--frozen-lockfile"]);
|
|
114983
116092
|
if (!result.ok) {
|
|
@@ -114987,7 +116096,7 @@ async function verifyLockfile(ctx, output2) {
|
|
|
114987
116096
|
}
|
|
114988
116097
|
async function assertPluginInstallable(ctx, output2) {
|
|
114989
116098
|
const smokePath = join43(ctx.repoRoot, "scripts", "commands", "plugin-install-smoke.ts");
|
|
114990
|
-
if (!
|
|
116099
|
+
if (!existsSync15(smokePath)) {
|
|
114991
116100
|
output2.write(" \u21B3 plugin-install-smoke: gate script not present in this repo \u2014 skipped");
|
|
114992
116101
|
return;
|
|
114993
116102
|
}
|
|
@@ -115028,7 +116137,7 @@ async function readJson2(path9) {
|
|
|
115028
116137
|
function rootWorkspaceGlobs(repoRoot) {
|
|
115029
116138
|
let root;
|
|
115030
116139
|
try {
|
|
115031
|
-
root = JSON.parse(
|
|
116140
|
+
root = JSON.parse(readFileSync15(join43(repoRoot, "package.json"), "utf8"));
|
|
115032
116141
|
} catch {
|
|
115033
116142
|
return [];
|
|
115034
116143
|
}
|
|
@@ -115046,10 +116155,10 @@ function workspaceDirs(repoRoot) {
|
|
|
115046
116155
|
continue;
|
|
115047
116156
|
const parent = glob.slice(0, -2);
|
|
115048
116157
|
const parentAbs = join43(repoRoot, parent);
|
|
115049
|
-
if (!
|
|
116158
|
+
if (!existsSync15(parentAbs))
|
|
115050
116159
|
continue;
|
|
115051
116160
|
for (const entry of readdirSync8(parentAbs, { withFileTypes: true })) {
|
|
115052
|
-
if (entry.isDirectory() &&
|
|
116161
|
+
if (entry.isDirectory() && existsSync15(join43(parentAbs, entry.name, "package.json"))) {
|
|
115053
116162
|
dirs.push(`${parent}/${entry.name}`);
|
|
115054
116163
|
}
|
|
115055
116164
|
}
|
|
@@ -115080,7 +116189,7 @@ async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerCon
|
|
|
115080
116189
|
for (const ws of workspaces2) {
|
|
115081
116190
|
const probe = findLiteralProbe(versionCarriers, ws.dir) ?? defaultLiteralProbe;
|
|
115082
116191
|
const literalProbe = join43(ws.dir, probe.file);
|
|
115083
|
-
const versionSourceFile =
|
|
116192
|
+
const versionSourceFile = existsSync15(join43(repoRoot, literalProbe)) ? literalProbe : undefined;
|
|
115084
116193
|
packages.set(shortName(ws.name), {
|
|
115085
116194
|
packageDir: ws.dir,
|
|
115086
116195
|
packageName: ws.name,
|
|
@@ -115144,7 +116253,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output2
|
|
|
115144
116253
|
if (changed.length === 0)
|
|
115145
116254
|
return changed;
|
|
115146
116255
|
const lockPath = join43(ctx.repoRoot, "bun.lock");
|
|
115147
|
-
if (
|
|
116256
|
+
if (existsSync15(lockPath)) {
|
|
115148
116257
|
const lock = await Bun.file(lockPath).text();
|
|
115149
116258
|
const staleSpec = `"${pkgName}": "workspace:${oldVersion}"`;
|
|
115150
116259
|
if (lock.includes(staleSpec)) {
|
|
@@ -115272,7 +116381,7 @@ async function bumpVersion(ctx, config4, version3, options, output2) {
|
|
|
115272
116381
|
await syncExtraCarriers(ctx, version3, staged, output2);
|
|
115273
116382
|
await verifyLockfile(ctx, output2);
|
|
115274
116383
|
const lockPath = join43(ctx.repoRoot, "bun.lock");
|
|
115275
|
-
if (
|
|
116384
|
+
if (existsSync15(lockPath) && Bun.file(lockPath).size > 0)
|
|
115276
116385
|
staged.push("bun.lock");
|
|
115277
116386
|
await git2(ctx.repoRoot, ["add", ...staged]);
|
|
115278
116387
|
const message = `${config4.releaseCommitType}(${config4.releaseCommitScope}): ${config4.releaseCommitSubject(version3)}`;
|
|
@@ -115350,7 +116459,7 @@ async function bumpAll(ctx, version3, options, output2) {
|
|
|
115350
116459
|
await syncExtraCarriers(ctx, version3, staged, output2);
|
|
115351
116460
|
await verifyLockfile(ctx, output2);
|
|
115352
116461
|
const lockPath = join43(ctx.repoRoot, "bun.lock");
|
|
115353
|
-
if (
|
|
116462
|
+
if (existsSync15(lockPath) && Bun.file(lockPath).size > 0)
|
|
115354
116463
|
staged.push("bun.lock");
|
|
115355
116464
|
await git2(ctx.repoRoot, ["add", ...staged]);
|
|
115356
116465
|
const shortNames = configs.map((c3) => c3.packageName.split("/").pop() ?? c3.packageName).join(" + ");
|
|
@@ -115787,8 +116896,15 @@ function registerFeatureCommand(program2, context4) {
|
|
|
115787
116896
|
const result = await svc.refresh({ featureId: options.feature });
|
|
115788
116897
|
if (options.json) {
|
|
115789
116898
|
const featuresDir = options.folder ?? (await resolvePlanningFolders(context4.fs)).featuresDir;
|
|
115790
|
-
context4.output.write(toEnvelopeJson({
|
|
116899
|
+
context4.output.write(toEnvelopeJson({
|
|
116900
|
+
index_path: `${featuresDir}/INDEX.md`,
|
|
116901
|
+
tasksUpdated: result.tasksUpdated,
|
|
116902
|
+
skipped: result.skipped
|
|
116903
|
+
}, { enveloped: options.jsonEnvelope }));
|
|
115791
116904
|
} else {
|
|
116905
|
+
for (const s3 of result.skipped) {
|
|
116906
|
+
context4.output.write(`Skipped ${s3.id} (${s3.reason})`);
|
|
116907
|
+
}
|
|
115792
116908
|
context4.output.write(`INDEX.md regenerated (${result.index.split(`
|
|
115793
116909
|
`).length} lines); ${result.tasksUpdated} feature Tasks region(s) updated`);
|
|
115794
116910
|
}
|
|
@@ -115797,7 +116913,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
115797
116913
|
context4.setExitCode(1);
|
|
115798
116914
|
}
|
|
115799
116915
|
});
|
|
115800
|
-
feature.command("check").summary("Validate feature file(s) through the four-layer check (design \xA73).").argument("[id]", "Feature ID (validates all features in the folder when omitted)").option(...SHARED_OPTIONS.strictFeature).option(...SHARED_OPTIONS.asFeature0418).option(...SHARED_OPTIONS.folderFeatures).option("--fix", "repair structural findings in place (heading presence/level/order, R-item checkboxes)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, options) => {
|
|
116916
|
+
feature.command("check").summary("Validate feature file(s) through the four-layer check (design \xA73).").argument("[id]", "Feature ID (validates all features in the folder when omitted)").option(...SHARED_OPTIONS.strictFeature).option(...SHARED_OPTIONS.asFeature0418).option(...SHARED_OPTIONS.folderFeatures).option("--fix", "repair structural findings in place (heading presence/level/order, R-item checkboxes)").option("--inventory <report>", "Cross-check the report requirement inventory against the AC covers lines (errors on uncovered items)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, options) => {
|
|
115801
116917
|
const resolved = await resolvePlanningFolders(context4.fs);
|
|
115802
116918
|
const featuresDir = options.folder ?? context4.fs.resolve(resolved.featuresDir);
|
|
115803
116919
|
const tasksDir = context4.fs.resolve(resolved.tasksDir);
|
|
@@ -115807,6 +116923,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
115807
116923
|
const svc = new FeatureCheckService(context4.fs);
|
|
115808
116924
|
const json4 = options.json === true;
|
|
115809
116925
|
const strict = options.strict === true;
|
|
116926
|
+
const inventoryText = typeof options.inventory === "string" ? await Promise.resolve(context4.fs.readFile(context4.fs.resolve(options.inventory))).catch(() => "") : undefined;
|
|
115810
116927
|
try {
|
|
115811
116928
|
const entries = await context4.fs.readDir(featuresDir);
|
|
115812
116929
|
const ids = id ? [id] : entries.map((n3) => n3.match(/^([A-Z][1-9]*)_.+\.md$/)?.[1]).filter((n3) => n3 !== undefined);
|
|
@@ -115827,7 +116944,8 @@ function registerFeatureCommand(program2, context4) {
|
|
|
115827
116944
|
receiptRunPort: await makeReceiptRunPort(context4),
|
|
115828
116945
|
severityOverrides: resolved.severityOverrides,
|
|
115829
116946
|
asStatus: options.as,
|
|
115830
|
-
fix: options.fix === true
|
|
116947
|
+
fix: options.fix === true,
|
|
116948
|
+
inventory: inventoryText
|
|
115831
116949
|
});
|
|
115832
116950
|
results.push(result);
|
|
115833
116951
|
if (!json4) {
|
|
@@ -115874,7 +116992,8 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
115874
116992
|
if (options.all) {
|
|
115875
116993
|
const result = await svc.syncAllFeatures({
|
|
115876
116994
|
dryRun: options.dryRun,
|
|
115877
|
-
forceConfirm: options.force
|
|
116995
|
+
forceConfirm: options.force,
|
|
116996
|
+
force: options.force
|
|
115878
116997
|
});
|
|
115879
116998
|
if (options.json) {
|
|
115880
116999
|
context4.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
@@ -115888,7 +117007,8 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
115888
117007
|
} else if (id) {
|
|
115889
117008
|
const result = await svc.syncFeature(id, {
|
|
115890
117009
|
dryRun: options.dryRun,
|
|
115891
|
-
forceConfirm: options.force
|
|
117010
|
+
forceConfirm: options.force,
|
|
117011
|
+
force: options.force
|
|
115892
117012
|
});
|
|
115893
117013
|
if (options.json) {
|
|
115894
117014
|
context4.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
@@ -115903,9 +117023,6 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
115903
117023
|
}
|
|
115904
117024
|
});
|
|
115905
117025
|
}
|
|
115906
|
-
function isFeatureFile(name, id) {
|
|
115907
|
-
return name.startsWith(`${id}_`) && name.endsWith(".md") && name.length > id.length + 4;
|
|
115908
|
-
}
|
|
115909
117026
|
async function makeReceiptRunPort(context4) {
|
|
115910
117027
|
const db2 = await context4.getDb();
|
|
115911
117028
|
const runs2 = new RunDao(db2);
|
|
@@ -115993,7 +117110,7 @@ import { createRequire } from "module";
|
|
|
115993
117110
|
var CLI_CONFIG = {
|
|
115994
117111
|
binaryName: "spur",
|
|
115995
117112
|
binaryLabel: "spur",
|
|
115996
|
-
binaryVersion: "0.3.
|
|
117113
|
+
binaryVersion: "0.3.96",
|
|
115997
117114
|
configDir: ".spur",
|
|
115998
117115
|
configFile: ".spur/config.yaml",
|
|
115999
117116
|
databaseFile: ".spur/spur.db"
|
|
@@ -117184,7 +118301,7 @@ function registerMigrateCommand(program2, context4, options = {}) {
|
|
|
117184
118301
|
init_src2();
|
|
117185
118302
|
init_dist5();
|
|
117186
118303
|
await init_src3();
|
|
117187
|
-
import { basename as
|
|
118304
|
+
import { basename as basename16, dirname as dirname26, join as join47, resolve as resolve20 } from "path";
|
|
117188
118305
|
function registerProjectsCommand(program2, context4) {
|
|
117189
118306
|
const projectsCmd = program2.command("projects").summary("manage the Spur multi-project registry");
|
|
117190
118307
|
projectsCmd.command("add").argument("<path>", "Project root directory path").option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).option(...SHARED_OPTIONS.jsonEnvelope).action(async (pathArg, options) => {
|
|
@@ -117193,7 +118310,7 @@ function registerProjectsCommand(program2, context4) {
|
|
|
117193
118310
|
if (!await context4.fs.exists(absolutePath)) {
|
|
117194
118311
|
throw new Error(`Directory does not exist: ${absolutePath}`);
|
|
117195
118312
|
}
|
|
117196
|
-
const name = options.name ??
|
|
118313
|
+
const name = options.name ?? basename16(absolutePath);
|
|
117197
118314
|
const registry3 = new ProjectRegistry;
|
|
117198
118315
|
const entry = await registry3.upsert({ path: absolutePath, name, port: 0 });
|
|
117199
118316
|
if (options.json) {
|
|
@@ -117732,7 +118849,7 @@ init_src();
|
|
|
117732
118849
|
init_loader();
|
|
117733
118850
|
init_src2();
|
|
117734
118851
|
await init_src3();
|
|
117735
|
-
import { basename as
|
|
118852
|
+
import { basename as basename18, dirname as dirname28, isAbsolute as isAbsolute9, join as join51 } from "path";
|
|
117736
118853
|
init_dist5();
|
|
117737
118854
|
|
|
117738
118855
|
// ../server/src/bootstrap.ts
|
|
@@ -123750,7 +124867,8 @@ var featureContract = {
|
|
|
123750
124867
|
summary: "Refresh feature index and kanban",
|
|
123751
124868
|
tags: ["feature"]
|
|
123752
124869
|
}).output(apiSuccessSchema(exports_external.object({
|
|
123753
|
-
rebuilt: exports_external.number().int().nonnegative()
|
|
124870
|
+
rebuilt: exports_external.number().int().nonnegative(),
|
|
124871
|
+
skipped: exports_external.array(exports_external.object({ id: exports_external.string(), reason: exports_external.string() }))
|
|
123754
124872
|
}))),
|
|
123755
124873
|
check: oc.route({
|
|
123756
124874
|
method: "POST",
|
|
@@ -124451,6 +125569,9 @@ var taskSummarySchema = exports_external.object({
|
|
|
124451
125569
|
featureId: exports_external.string().nullable().optional(),
|
|
124452
125570
|
parentWbs: exports_external.string().regex(/^\d{4}$/).nullable().optional(),
|
|
124453
125571
|
type: exports_external.enum(TASK_TYPES).optional(),
|
|
125572
|
+
template: exports_external.string().optional(),
|
|
125573
|
+
estimateHours: exports_external.number().optional(),
|
|
125574
|
+
dependencyCount: exports_external.number().optional(),
|
|
124454
125575
|
filePath: exports_external.string(),
|
|
124455
125576
|
updatedAt: exports_external.string().optional()
|
|
124456
125577
|
});
|
|
@@ -124731,8 +125852,8 @@ function createFeatureHandlers(ctx) {
|
|
|
124731
125852
|
return { ok: true, data: { id: input.id, status: input.toStatus } };
|
|
124732
125853
|
}),
|
|
124733
125854
|
refresh: os2.feature.refresh.handler(async () => {
|
|
124734
|
-
const { tasksUpdated } = await ctx.featureService().refresh();
|
|
124735
|
-
return { ok: true, data: { rebuilt: tasksUpdated } };
|
|
125855
|
+
const { tasksUpdated, skipped } = await ctx.featureService().refresh();
|
|
125856
|
+
return { ok: true, data: { rebuilt: tasksUpdated, skipped } };
|
|
124736
125857
|
}),
|
|
124737
125858
|
check: os2.feature.check.handler(async ({ input }) => {
|
|
124738
125859
|
const folders = ctx.planningFolders();
|
|
@@ -124814,7 +125935,7 @@ init_src2();
|
|
|
124814
125935
|
init_dist5();
|
|
124815
125936
|
await init_src3();
|
|
124816
125937
|
import { homedir as homedir12 } from "os";
|
|
124817
|
-
import { basename as
|
|
125938
|
+
import { basename as basename17, join as join48, relative as relative7, resolve as resolve22 } from "path";
|
|
124818
125939
|
var startedAt = Date.now();
|
|
124819
125940
|
var OPERATOR_AGENT_ID = "board-operator";
|
|
124820
125941
|
var healthModule = {
|
|
@@ -124841,7 +125962,7 @@ var healthModule = {
|
|
|
124841
125962
|
return c3.json({ status: "error", db: "unreachable" }, 503);
|
|
124842
125963
|
});
|
|
124843
125964
|
app.get("/api/project", (c3) => {
|
|
124844
|
-
return c3.json({ name: ctx ?
|
|
125965
|
+
return c3.json({ name: ctx ? basename17(ctx.cwd) : null, path: ctx ? normalizeProjectPath(ctx.cwd) : null });
|
|
124845
125966
|
});
|
|
124846
125967
|
app.get("/api/project/fleet", async (c3) => {
|
|
124847
125968
|
if (!ctx) {
|
|
@@ -125473,7 +126594,7 @@ var healthModule = {
|
|
|
125473
126594
|
return c3.json({ ok: false, error: "File not found" }, 404);
|
|
125474
126595
|
}
|
|
125475
126596
|
const content = await fs3.readFile(fullPath);
|
|
125476
|
-
let title2 =
|
|
126597
|
+
let title2 = basename17(reqPath, ".md");
|
|
125477
126598
|
const match2 = content.match(/^#\s+(.+)$/m);
|
|
125478
126599
|
if (match2?.[1])
|
|
125479
126600
|
title2 = match2[1].trim();
|
|
@@ -125563,7 +126684,7 @@ var healthModule = {
|
|
|
125563
126684
|
return c3.json({ ok: false, error: "File not found" }, 404);
|
|
125564
126685
|
}
|
|
125565
126686
|
const content = await fs3.readFile(fullPath);
|
|
125566
|
-
let title2 =
|
|
126687
|
+
let title2 = basename17(reqPath, ".md");
|
|
125567
126688
|
const match2 = content.match(/^#\s+(.+)$/m);
|
|
125568
126689
|
if (match2?.[1])
|
|
125569
126690
|
title2 = match2[1].trim();
|
|
@@ -126419,6 +127540,8 @@ function createTaskHandlers(ctx) {
|
|
|
126419
127540
|
const tasks = await ctx.taskService().list(filters);
|
|
126420
127541
|
const data = tasks.map((t) => {
|
|
126421
127542
|
const fm = t.frontmatter ?? {};
|
|
127543
|
+
const estimateHours = typeof fm.estimate_hours === "number" ? fm.estimate_hours : typeof fm.estimated_hours === "number" ? fm.estimated_hours : undefined;
|
|
127544
|
+
const deps = Array.isArray(fm.dependencies) ? fm.dependencies : [];
|
|
126422
127545
|
return {
|
|
126423
127546
|
wbs: t.wbs,
|
|
126424
127547
|
name: t.name,
|
|
@@ -126427,6 +127550,9 @@ function createTaskHandlers(ctx) {
|
|
|
126427
127550
|
featureId: fm.feature_id ?? undefined,
|
|
126428
127551
|
parentWbs: fm.parent_wbs ?? undefined,
|
|
126429
127552
|
type: TASK_TYPES.includes(fm.type) ? fm.type : undefined,
|
|
127553
|
+
template: typeof fm.template === "string" ? fm.template : undefined,
|
|
127554
|
+
estimateHours,
|
|
127555
|
+
dependencyCount: deps.length > 0 ? deps.length : undefined,
|
|
126430
127556
|
filePath: t.filePath,
|
|
126431
127557
|
updatedAt: fm.updated_at
|
|
126432
127558
|
};
|
|
@@ -129453,7 +130579,7 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
129453
130579
|
});
|
|
129454
130580
|
const projectRegistry = new ProjectRegistry;
|
|
129455
130581
|
const projectCwd = projectRoot;
|
|
129456
|
-
const projectName =
|
|
130582
|
+
const projectName = basename18(projectCwd);
|
|
129457
130583
|
try {
|
|
129458
130584
|
const existingProject = await projectRegistry.getByPath(projectCwd);
|
|
129459
130585
|
await projectRegistry.upsert({
|
|
@@ -129732,7 +130858,7 @@ init_src();
|
|
|
129732
130858
|
init_dist7();
|
|
129733
130859
|
init_dist5();
|
|
129734
130860
|
await init_src3();
|
|
129735
|
-
import { closeSync as
|
|
130861
|
+
import { closeSync as closeSync7, fstatSync as fstatSync4, openSync as openSync7, readFileSync as readFileSync16, readSync as readSync3 } from "fs";
|
|
129736
130862
|
import { join as join54 } from "path";
|
|
129737
130863
|
import { createInterface } from "readline";
|
|
129738
130864
|
import { setTimeout as sleep6 } from "timers/promises";
|
|
@@ -130860,7 +131986,7 @@ function isTerminalTraceStatus(status) {
|
|
|
130860
131986
|
function readRunLogChunk(logPath, offset) {
|
|
130861
131987
|
let fd;
|
|
130862
131988
|
try {
|
|
130863
|
-
fd =
|
|
131989
|
+
fd = openSync7(logPath, "r");
|
|
130864
131990
|
} catch {
|
|
130865
131991
|
return { exists: false, lines: [], offset };
|
|
130866
131992
|
}
|
|
@@ -130879,7 +132005,7 @@ function readRunLogChunk(logPath, offset) {
|
|
|
130879
132005
|
const lines = parts.length > 0 && parts.at(-1) === "" ? parts.slice(0, -1) : parts;
|
|
130880
132006
|
return { exists: true, lines, offset: offset + consumed };
|
|
130881
132007
|
} finally {
|
|
130882
|
-
|
|
132008
|
+
closeSync7(fd);
|
|
130883
132009
|
}
|
|
130884
132010
|
}
|
|
130885
132011
|
async function followRunLog(service, runId, dir, pollMs, write, wait = (ms) => sleep6(ms), now2 = Date.now, deadlineMs) {
|
|
@@ -130901,7 +132027,7 @@ async function followRunLog(service, runId, dir, pollMs, write, wait = (ms) => s
|
|
|
130901
132027
|
offset = 0;
|
|
130902
132028
|
if (carried !== "") {
|
|
130903
132029
|
try {
|
|
130904
|
-
const nextText =
|
|
132030
|
+
const nextText = readFileSync16(target, "utf8");
|
|
130905
132031
|
if (nextText.startsWith(carried))
|
|
130906
132032
|
offset = Buffer.byteLength(carried);
|
|
130907
132033
|
} catch {
|