@planu/cli 4.11.9 → 4.12.2
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/CHANGELOG.md +129 -22
- package/dist/engine/abortable-process-runner.d.ts +8 -0
- package/dist/engine/abortable-process-runner.js +107 -0
- package/dist/engine/cascade-hooks/core/append-releases.d.ts +1 -0
- package/dist/engine/cascade-hooks/core/append-releases.js +51 -14
- package/dist/engine/cascade-hooks/hooks/orchestration-plan.hook.js +16 -21
- package/dist/engine/ci-generator/yaml-builder.js +5 -1
- package/dist/engine/context-md-generator.js +3 -1
- package/dist/engine/criterion-identity.d.ts +6 -0
- package/dist/engine/criterion-identity.js +63 -0
- package/dist/engine/evidence-gates/lifecycle-gate.js +22 -44
- package/dist/engine/evidence-index/index-builder.js +7 -19
- package/dist/engine/frontmatter-parser.d.ts +5 -0
- package/dist/engine/frontmatter-parser.js +24 -0
- package/dist/engine/project-command-runtime.d.ts +5 -0
- package/dist/{tools/validate-runtime.js → engine/project-command-runtime.js} +8 -21
- package/dist/engine/project-graph/builder.js +1 -35
- package/dist/engine/project-graph/cache.js +37 -1
- package/dist/engine/qa-gate.d.ts +4 -1
- package/dist/engine/qa-gate.js +72 -59
- package/dist/engine/safety/cross-process-lock.d.ts +1 -6
- package/dist/engine/safety/cross-process-lock.js +18 -16
- package/dist/engine/safety/lock-scavenger.d.ts +9 -0
- package/dist/engine/safety/lock-scavenger.js +114 -0
- package/dist/engine/session-context-generator.js +24 -2
- package/dist/engine/session-safeguard/learnings-buffer.js +7 -2
- package/dist/engine/session-state/writer.js +15 -8
- package/dist/engine/skill-registry/installer.js +1 -1
- package/dist/engine/spec-format/acceptance-criteria.js +8 -26
- package/dist/engine/spec-grounding/contract.d.ts +3 -1
- package/dist/engine/spec-grounding/contract.js +39 -14
- package/dist/engine/universal-rules/installer.js +2 -2
- package/dist/engine/validation-evidence-digest.d.ts +4 -0
- package/dist/engine/validation-evidence-digest.js +83 -0
- package/dist/engine/validation-evidence-ledger.d.ts +30 -0
- package/dist/engine/validation-evidence-ledger.js +274 -0
- package/dist/engine/validation-impact-planner.d.ts +4 -0
- package/dist/engine/validation-impact-planner.js +71 -0
- package/dist/engine/validator/spec-compliance-runner.d.ts +1 -1
- package/dist/engine/validator/spec-compliance-runner.js +37 -40
- package/dist/engine/validator/validation-report-writer.d.ts +1 -0
- package/dist/engine/validator/validation-report-writer.js +1 -1
- package/dist/engine/validator.js +7 -11
- package/dist/storage/convention-baseline.js +5 -0
- package/dist/storage/session-state-store.js +13 -6
- package/dist/storage/spec-store.d.ts +5 -0
- package/dist/storage/spec-store.js +7 -0
- package/dist/tools/git/git-helpers.d.ts +5 -1
- package/dist/tools/git/git-helpers.js +3 -2
- package/dist/tools/init-project/conventions-writer.js +1 -1
- package/dist/tools/init-project/scaffold-writer.js +1 -1
- package/dist/tools/safe-handler.d.ts +5 -1
- package/dist/tools/safe-handler.js +73 -12
- package/dist/tools/status-handler.d.ts +1 -1
- package/dist/tools/status-handler.js +27 -66
- package/dist/tools/update-status/dod-gates.js +7 -31
- package/dist/tools/update-status/evidence-gate.js +2 -4
- package/dist/tools/update-status/force-done-audit.d.ts +2 -0
- package/dist/tools/update-status/force-done-audit.js +23 -0
- package/dist/tools/update-status/qa-gate.d.ts +1 -0
- package/dist/tools/update-status/qa-gate.js +7 -51
- package/dist/tools/update-status-convention-gate.js +132 -97
- package/dist/tools/validate-lint.d.ts +1 -1
- package/dist/tools/validate-lint.js +78 -165
- package/dist/tools/validate.js +3 -1
- package/dist/transports/http-transport.js +22 -8
- package/dist/transports/mcp-types.d.ts +2 -2
- package/dist/transports/transport-factory.js +110 -12
- package/dist/types/abortable-process.d.ts +26 -0
- package/dist/types/abortable-process.js +2 -0
- package/dist/types/criterion-identity.d.ts +12 -0
- package/dist/types/criterion-identity.js +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/qa-gate.d.ts +0 -5
- package/dist/types/spec-format.d.ts +2 -0
- package/dist/types/spec-lock-v2.d.ts +7 -0
- package/dist/types/validation-evidence.d.ts +62 -0
- package/dist/types/validation-evidence.js +2 -0
- package/package.json +43 -23
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/dist/storage/qa-gate-store.d.ts +0 -4
- package/dist/storage/qa-gate-store.js +0 -24
- package/dist/tools/validate-runtime.d.ts +0 -14
- package/dist/types/data/estimation.d.ts +0 -147
- package/dist/types/data/estimation.js +0 -2
- package/dist/types/data/index.d.ts +0 -5
- package/dist/types/data/index.js +0 -6
- package/dist/types/data/velocity.d.ts +0 -168
- package/dist/types/data/velocity.js +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,110 @@
|
|
|
1
|
+
## [4.12.2] - 2026-07-27
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix: restore dark language menu contrast
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## [4.12.1] - 2026-07-27
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
- fix: prevent personal metadata exposure (#62)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [4.12.0] - 2026-07-27
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
- feat(website): add multichain donations (#61)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [4.11.20] - 2026-07-27
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
- fix(website): align desktop landing controls (#60)
|
|
23
|
+
- fix(release): bind reproducibility to annotated tags (#59)
|
|
24
|
+
|
|
25
|
+
### Chores
|
|
26
|
+
- chore(planu): close SPEC-1155 lifecycle
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## [4.11.19] - 2026-07-27
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
- fix(website): repair responsive visual system (#58)
|
|
33
|
+
- fix(website): close production delivery gaps (#57)
|
|
34
|
+
|
|
35
|
+
### Refactoring
|
|
36
|
+
- refactor(website): simplify landing around real evidence (#55)
|
|
37
|
+
|
|
38
|
+
### Chores
|
|
39
|
+
- chore(planu): close SPEC-1153
|
|
40
|
+
- chore(planu): close SPEC-1151 lifecycle
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## [4.11.18] - 2026-07-26
|
|
44
|
+
|
|
45
|
+
### Bug Fixes
|
|
46
|
+
- fix(security): remove exposed receipt from website
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## [4.11.17] - 2026-07-26
|
|
50
|
+
|
|
51
|
+
### Bug Fixes
|
|
52
|
+
- fix: clear released specs from pending ledger
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## [4.11.16] - 2026-07-26
|
|
56
|
+
|
|
57
|
+
### Bug Fixes
|
|
58
|
+
- fix: restore trustworthy maintenance audit gates
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## [4.11.15] - 2026-07-26
|
|
62
|
+
|
|
63
|
+
### Bug Fixes
|
|
64
|
+
- fix: stabilize project graph freshness (#51)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## [4.11.14] - 2026-07-25
|
|
68
|
+
|
|
69
|
+
### Bug Fixes
|
|
70
|
+
- fix: parse canonical active spec status (#50)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## [4.11.13] - 2026-07-25
|
|
74
|
+
|
|
75
|
+
### Chores
|
|
76
|
+
- chore(deps): update ESLint to 10.8.0 (#49)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## [4.11.12] - 2026-07-25
|
|
80
|
+
|
|
81
|
+
### Bug Fixes
|
|
82
|
+
- fix(deps): remediate brace-expansion advisory
|
|
83
|
+
|
|
84
|
+
### Chores
|
|
85
|
+
- chore(planu): close SPEC-1142 lifecycle
|
|
86
|
+
- chore(planu): close SPEC-1141 lifecycle
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## [4.11.11] - 2026-07-24
|
|
90
|
+
|
|
91
|
+
### Bug Fixes
|
|
92
|
+
- fix: finalize release state reconciliation
|
|
93
|
+
- fix: preserve pnpm policy in Docker builds
|
|
94
|
+
|
|
95
|
+
### Chores
|
|
96
|
+
- chore(deps): refresh routine dependencies
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## [4.11.10] - 2026-07-24
|
|
100
|
+
|
|
101
|
+
### Bug Fixes
|
|
102
|
+
- fix: preserve verified release artifacts
|
|
103
|
+
|
|
104
|
+
### Chores
|
|
105
|
+
- chore: harden release and state reconciliation
|
|
106
|
+
|
|
107
|
+
|
|
1
108
|
## [4.11.9] - 2026-07-20
|
|
2
109
|
|
|
3
110
|
### Bug Fixes
|
|
@@ -4124,54 +4231,54 @@ Total: 275 tools (72 free + 192 pro + 11 always-on)
|
|
|
4124
4231
|
|
|
4125
4232
|
* add website tsconfig.json to avoid ES2024 esbuild error ([8184375](https://github.com/planu-dev/planu/commit/818437593775eac2ec56dd863f895c3390254783))
|
|
4126
4233
|
|
|
4127
|
-
## [0.15.1](https://github.com/
|
|
4234
|
+
## [0.15.1](https://github.com/imaintainer92/specforge-mcp/compare/v0.15.0...v0.15.1) (2026-03-02)
|
|
4128
4235
|
|
|
4129
4236
|
|
|
4130
4237
|
### Bug Fixes
|
|
4131
4238
|
|
|
4132
|
-
* **npm:** publish under [@specforge-tools](https://github.com/specforge-tools) org to replace personal account ([adadff0](https://github.com/
|
|
4239
|
+
* **npm:** publish under [@specforge-tools](https://github.com/specforge-tools) org to replace personal account ([adadff0](https://github.com/imaintainer92/specforge-mcp/commit/adadff0dbeabd5b15386715a4c26e26cdfff1593))
|
|
4133
4240
|
|
|
4134
|
-
# [0.15.0](https://github.com/
|
|
4241
|
+
# [0.15.0](https://github.com/imaintainer92/specforge-mcp/compare/v0.14.1...v0.15.0) (2026-03-02)
|
|
4135
4242
|
|
|
4136
4243
|
|
|
4137
4244
|
### Features
|
|
4138
4245
|
|
|
4139
|
-
* **licensing:** implement Lemon Squeezy license enforcement system ([bcd99d9](https://github.com/
|
|
4246
|
+
* **licensing:** implement Lemon Squeezy license enforcement system ([bcd99d9](https://github.com/imaintainer92/specforge-mcp/commit/bcd99d9c2331335874dccd5a8a08bae358a87486))
|
|
4140
4247
|
|
|
4141
|
-
## [0.14.1](https://github.com/
|
|
4248
|
+
## [0.14.1](https://github.com/imaintainer92/specforge-mcp/compare/v0.14.0...v0.14.1) (2026-03-02)
|
|
4142
4249
|
|
|
4143
4250
|
|
|
4144
4251
|
### Bug Fixes
|
|
4145
4252
|
|
|
4146
|
-
* **website:** disable empty aside sidebar on pricing pages ([440dfb2](https://github.com/
|
|
4253
|
+
* **website:** disable empty aside sidebar on pricing pages ([440dfb2](https://github.com/imaintainer92/specforge-mcp/commit/440dfb2dbcc60aa9393ea9954dfccb03d2515617))
|
|
4147
4254
|
|
|
4148
|
-
# [0.14.0](https://github.com/
|
|
4255
|
+
# [0.14.0](https://github.com/imaintainer92/specforge-mcp/compare/v0.13.4...v0.14.0) (2026-03-02)
|
|
4149
4256
|
|
|
4150
4257
|
|
|
4151
4258
|
### Features
|
|
4152
4259
|
|
|
4153
|
-
* **website:** redesign homepage sections, pricing cards, and fix broken links ([cc802d9](https://github.com/
|
|
4260
|
+
* **website:** redesign homepage sections, pricing cards, and fix broken links ([cc802d9](https://github.com/imaintainer92/specforge-mcp/commit/cc802d911c499341b4f6c54b6d708bd2a0c891ec))
|
|
4154
4261
|
|
|
4155
|
-
## [0.13.4](https://github.com/
|
|
4262
|
+
## [0.13.4](https://github.com/imaintainer92/specforge-mcp/compare/v0.13.3...v0.13.4) (2026-03-02)
|
|
4156
4263
|
|
|
4157
4264
|
|
|
4158
4265
|
### Bug Fixes
|
|
4159
4266
|
|
|
4160
|
-
* **website:** add vercel.json with cleanUrls for proper URL routing ([13878b5](https://github.com/
|
|
4267
|
+
* **website:** add vercel.json with cleanUrls for proper URL routing ([13878b5](https://github.com/imaintainer92/specforge-mcp/commit/13878b563c64fa24f11a0e7feda034e2dec807c8))
|
|
4161
4268
|
|
|
4162
|
-
## [0.13.3](https://github.com/
|
|
4269
|
+
## [0.13.3](https://github.com/imaintainer92/specforge-mcp/compare/v0.13.2...v0.13.3) (2026-03-02)
|
|
4163
4270
|
|
|
4164
4271
|
|
|
4165
4272
|
### Bug Fixes
|
|
4166
4273
|
|
|
4167
|
-
* **website:** enable cleanUrls to fix 11 empty pages on Vercel ([6714a52](https://github.com/
|
|
4274
|
+
* **website:** enable cleanUrls to fix 11 empty pages on Vercel ([6714a52](https://github.com/imaintainer92/specforge-mcp/commit/6714a5252cb358af7b2e95c980e873836efd4544))
|
|
4168
4275
|
|
|
4169
|
-
## [0.13.2](https://github.com/
|
|
4276
|
+
## [0.13.2](https://github.com/imaintainer92/specforge-mcp/compare/v0.13.1...v0.13.2) (2026-03-02)
|
|
4170
4277
|
|
|
4171
4278
|
|
|
4172
4279
|
### Bug Fixes
|
|
4173
4280
|
|
|
4174
|
-
* **ci:** add @semantic-release/changelog to auto-update CHANGELOG.md ([92834a3](https://github.com/
|
|
4281
|
+
* **ci:** add @semantic-release/changelog to auto-update CHANGELOG.md ([92834a3](https://github.com/imaintainer92/specforge-mcp/commit/92834a32440df8e7b3c62c7b0ad03c29ed99089d))
|
|
4175
4282
|
|
|
4176
4283
|
# Changelog
|
|
4177
4284
|
|
|
@@ -4192,30 +4299,30 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
|
|
|
4192
4299
|
- Duplicate `Architecture` row appearing outside the metadata table in FICHA-TECNICA.md
|
|
4193
4300
|
- Structured multi-paragraph descriptions no longer wrapped in "As a user, I want..." in HU.md
|
|
4194
4301
|
|
|
4195
|
-
## [0.13.1](https://github.com/
|
|
4302
|
+
## [0.13.1](https://github.com/imaintainer92/specforge-mcp/compare/v0.13.0...v0.13.1) — 2026-03-01
|
|
4196
4303
|
|
|
4197
4304
|
### Fixed
|
|
4198
4305
|
- **website:** Redesign PricingCards and clarify technical terms in copy
|
|
4199
4306
|
|
|
4200
|
-
## [0.13.0](https://github.com/
|
|
4307
|
+
## [0.13.0](https://github.com/imaintainer92/specforge-mcp/compare/v0.12.1...v0.13.0) — 2026-03-01
|
|
4201
4308
|
|
|
4202
4309
|
### Added
|
|
4203
4310
|
- **website:** Replace donation system with pricing page and visual components
|
|
4204
4311
|
- Auto-sync script for tool count across website and smoke test
|
|
4205
4312
|
- Documentation updated to reflect 60 tools after context_budget addition
|
|
4206
4313
|
|
|
4207
|
-
## [0.12.1](https://github.com/
|
|
4314
|
+
## [0.12.1](https://github.com/imaintainer92/specforge-mcp/compare/v0.12.0...v0.12.1) — 2026-02-28
|
|
4208
4315
|
|
|
4209
4316
|
### Fixed
|
|
4210
4317
|
- **tests:** Update smoke test tool count to 60 after context_budget addition
|
|
4211
4318
|
|
|
4212
|
-
## [0.12.0](https://github.com/
|
|
4319
|
+
## [0.12.0](https://github.com/imaintainer92/specforge-mcp/compare/v0.11.1...v0.12.0) — 2026-02-27
|
|
4213
4320
|
|
|
4214
4321
|
### Added
|
|
4215
4322
|
- **tools:** `context_budget` tool for context window optimization (SPEC-063)
|
|
4216
4323
|
- 60 MCP tools total
|
|
4217
4324
|
|
|
4218
|
-
## [0.11.1](https://github.com/
|
|
4325
|
+
## [0.11.1](https://github.com/imaintainer92/specforge-mcp/compare/v0.11.0...v0.11.1) — 2026-02-27
|
|
4219
4326
|
|
|
4220
4327
|
### Fixed
|
|
4221
4328
|
- **ux:** Improve `summarize_spec` and `generate_docs` tool descriptions to prevent LLM bypass
|
|
@@ -4223,7 +4330,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
|
|
|
4223
4330
|
### Changed
|
|
4224
4331
|
- Refactor: split 5 files near 400L limit into subdirectories
|
|
4225
4332
|
|
|
4226
|
-
## [0.11.0](https://github.com/
|
|
4333
|
+
## [0.11.0](https://github.com/imaintainer92/specforge-mcp/compare/v0.10.0...v0.11.0) — 2026-02-27
|
|
4227
4334
|
|
|
4228
4335
|
### Added
|
|
4229
4336
|
- **engine:** Executive summary HTML export (SPEC-060)
|
|
@@ -4231,13 +4338,13 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
|
|
|
4231
4338
|
- **engine:** Structured risk, complexity and trade-off documentation (SPEC-062)
|
|
4232
4339
|
- 213 new tests across 54 files (+5,618 lines)
|
|
4233
4340
|
|
|
4234
|
-
## [0.10.0](https://github.com/
|
|
4341
|
+
## [0.10.0](https://github.com/imaintainer92/specforge-mcp/compare/v0.9.0...v0.10.0) — 2026-02-27
|
|
4235
4342
|
|
|
4236
4343
|
### Added
|
|
4237
4344
|
- **ux:** Git branch suggestions in MCP workflow (e.g., `feat/SPEC-XXX-name` after creating specs)
|
|
4238
4345
|
- **ux:** Git Flow enforcement — remind users to work in dedicated branches
|
|
4239
4346
|
|
|
4240
|
-
## [0.9.0](https://github.com/
|
|
4347
|
+
## [0.9.0](https://github.com/imaintainer92/specforge-mcp/compare/v0.8.0...v0.9.0) — 2026-02-26
|
|
4241
4348
|
|
|
4242
4349
|
### Added
|
|
4243
4350
|
- **ux:** Global server instructions with plain-language glossary for all technical terms
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AbortableProcessOptions, AbortableProcessResult } from '../types/abortable-process.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a validation command in its own process group. Cancellation and timeouts
|
|
4
|
+
* terminate the complete group and settle only after the child has closed, so a
|
|
5
|
+
* cancelled gate cannot keep writing output or publish late evidence.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runAbortableProcess(command: string, args: string[], options: AbortableProcessOptions): Promise<AbortableProcessResult>;
|
|
8
|
+
//# sourceMappingURL=abortable-process-runner.d.ts.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a validation command in its own process group. Cancellation and timeouts
|
|
4
|
+
* terminate the complete group and settle only after the child has closed, so a
|
|
5
|
+
* cancelled gate cannot keep writing output or publish late evidence.
|
|
6
|
+
*/
|
|
7
|
+
export function runAbortableProcess(command, args, options) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
if (options.signal?.aborted === true) {
|
|
10
|
+
resolve({
|
|
11
|
+
stdout: '',
|
|
12
|
+
stderr: '',
|
|
13
|
+
status: null,
|
|
14
|
+
signal: null,
|
|
15
|
+
error: abortError(options.signal.reason),
|
|
16
|
+
timedOut: false,
|
|
17
|
+
aborted: true,
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const child = spawn(command, args, {
|
|
22
|
+
cwd: options.cwd,
|
|
23
|
+
detached: process.platform !== 'win32',
|
|
24
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
25
|
+
});
|
|
26
|
+
let stdout = '';
|
|
27
|
+
let stderr = '';
|
|
28
|
+
let settled = false;
|
|
29
|
+
let timedOut = false;
|
|
30
|
+
let aborted = false;
|
|
31
|
+
let processError;
|
|
32
|
+
let hardStop;
|
|
33
|
+
const append = (current, chunk) => `${current}${chunk.toString('utf8')}`.slice(-options.maxBufferBytes);
|
|
34
|
+
child.stdout.on('data', (chunk) => {
|
|
35
|
+
stdout = append(stdout, chunk);
|
|
36
|
+
});
|
|
37
|
+
child.stderr.on('data', (chunk) => {
|
|
38
|
+
stderr = append(stderr, chunk);
|
|
39
|
+
});
|
|
40
|
+
const terminateTree = () => {
|
|
41
|
+
if (child.pid === undefined) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (process.platform === 'win32') {
|
|
45
|
+
try {
|
|
46
|
+
execFileSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' });
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
child.kill('SIGKILL');
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
child.kill('SIGTERM');
|
|
58
|
+
}
|
|
59
|
+
hardStop = setTimeout(() => {
|
|
60
|
+
try {
|
|
61
|
+
process.kill(-(child.pid ?? 0), 'SIGKILL');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
child.kill('SIGKILL');
|
|
65
|
+
}
|
|
66
|
+
}, 250);
|
|
67
|
+
hardStop.unref();
|
|
68
|
+
};
|
|
69
|
+
const onAbort = () => {
|
|
70
|
+
aborted = true;
|
|
71
|
+
processError = abortError(options.signal?.reason);
|
|
72
|
+
terminateTree();
|
|
73
|
+
};
|
|
74
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
timedOut = true;
|
|
77
|
+
processError = new Error(`Process timed out after ${String(options.timeoutMs)}ms`);
|
|
78
|
+
terminateTree();
|
|
79
|
+
}, options.timeoutMs);
|
|
80
|
+
const finish = (status, closeSignal) => {
|
|
81
|
+
if (settled) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
settled = true;
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
clearTimeout(hardStop);
|
|
87
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
88
|
+
resolve({
|
|
89
|
+
stdout,
|
|
90
|
+
stderr,
|
|
91
|
+
status,
|
|
92
|
+
signal: closeSignal,
|
|
93
|
+
...(processError ? { error: processError } : {}),
|
|
94
|
+
timedOut,
|
|
95
|
+
aborted,
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
child.on('error', (error) => {
|
|
99
|
+
processError = error;
|
|
100
|
+
});
|
|
101
|
+
child.on('close', finish);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function abortError(reason) {
|
|
105
|
+
return reason instanceof Error ? reason : new Error('Validation process aborted');
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=abortable-process-runner.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CascadeContext, CoreActionResult, PendingReleaseEntry } from '../../../types/cascade-hooks.js';
|
|
2
2
|
export declare function normalizePendingReleaseEntries(value: unknown, now?: Date): PendingReleaseEntry[];
|
|
3
|
+
export declare function reconcilePublishedPendingEntries(entries: readonly PendingReleaseEntry[], latestPublishedAt: string | null): PendingReleaseEntry[];
|
|
3
4
|
export declare function appendReleasesPending(ctx: CascadeContext): Promise<CoreActionResult>;
|
|
4
5
|
//# sourceMappingURL=append-releases.d.ts.map
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
// Appends spec to releases/pending.json within a 2-second AbortController budget.
|
|
3
3
|
// Logic extracted verbatim from src/tools/update-status-actions.ts (SPEC-649/SPEC-660 block).
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { readFile,
|
|
5
|
+
import { readFile, mkdir } from 'node:fs/promises';
|
|
6
|
+
import { execFile as execFileCallback } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
6
8
|
import { specStore } from '../../../storage/index.js';
|
|
9
|
+
import { withPendingReleaseLock, writePendingReleaseLedger, } from '../../../../scripts/lib/pending-release-file.mjs';
|
|
7
10
|
const CORE_ACTION_BUDGET_MS = 2000;
|
|
8
11
|
const PENDING_ENTRY_MAX_AGE_DAYS = 30;
|
|
12
|
+
const execFile = promisify(execFileCallback);
|
|
9
13
|
function isValidPendingReleaseEntry(value) {
|
|
10
14
|
return (typeof value === 'object' &&
|
|
11
15
|
value !== null &&
|
|
@@ -34,6 +38,35 @@ export function normalizePendingReleaseEntries(value, now = new Date()) {
|
|
|
34
38
|
}
|
|
35
39
|
return [...deduped.values()];
|
|
36
40
|
}
|
|
41
|
+
export function reconcilePublishedPendingEntries(entries, latestPublishedAt) {
|
|
42
|
+
if (!latestPublishedAt || !/^\d{4}-\d{2}-\d{2}/.test(latestPublishedAt)) {
|
|
43
|
+
return [...entries];
|
|
44
|
+
}
|
|
45
|
+
const releaseDay = latestPublishedAt.slice(0, 10);
|
|
46
|
+
// Date-only completion metadata cannot prove ordering within the release day.
|
|
47
|
+
// Preserve same-day entries fail-closed; a later release can reconcile them safely.
|
|
48
|
+
return entries.filter((entry) => entry.completedAt.slice(0, 10) >= releaseDay);
|
|
49
|
+
}
|
|
50
|
+
async function readLatestPublishedReleaseDate(projectPath) {
|
|
51
|
+
try {
|
|
52
|
+
const manifest = JSON.parse(await readFile(join(projectPath, 'package.json'), 'utf-8'));
|
|
53
|
+
if (typeof manifest.version !== 'string' ||
|
|
54
|
+
!/^\d+\.\d+\.\d+(?:[-+].*)?$/.test(manifest.version)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const tag = `v${manifest.version}`;
|
|
58
|
+
const { stdout } = await execFile('git', ['show', '-s', '--format=%cI', tag], {
|
|
59
|
+
cwd: projectPath,
|
|
60
|
+
timeout: 1_000,
|
|
61
|
+
maxBuffer: 16 * 1024,
|
|
62
|
+
});
|
|
63
|
+
const publishedAt = stdout.trim();
|
|
64
|
+
return Number.isNaN(Date.parse(publishedAt)) ? null : publishedAt;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
37
70
|
async function actuallyAppendReleasesPending(ctx, opts) {
|
|
38
71
|
const { projectPath, projectId, specId } = ctx;
|
|
39
72
|
if (!projectPath || ctx.newStatus !== 'done') {
|
|
@@ -51,19 +84,23 @@ async function actuallyAppendReleasesPending(ctx, opts) {
|
|
|
51
84
|
(async () => {
|
|
52
85
|
await mkdir(releasesDir, { recursive: true });
|
|
53
86
|
const spec = await specStore.getSpec(projectId, specId);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
const latestPublishedAt = await readLatestPublishedReleaseDate(projectPath);
|
|
88
|
+
await withPendingReleaseLock(pendingPath, { timeoutMs: 1_500, signal: opts.signal }, async () => {
|
|
89
|
+
let pendingList = [];
|
|
90
|
+
try {
|
|
91
|
+
const raw = await readFile(pendingPath, 'utf-8');
|
|
92
|
+
const parsed = JSON.parse(raw);
|
|
93
|
+
pendingList = normalizePendingReleaseEntries(parsed);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* file doesn't exist yet — start fresh */
|
|
97
|
+
}
|
|
98
|
+
pendingList = reconcilePublishedPendingEntries(pendingList, latestPublishedAt);
|
|
99
|
+
const completedAt = new Date().toISOString().substring(0, 10);
|
|
100
|
+
pendingList = pendingList.filter((entry) => entry.specId !== specId);
|
|
101
|
+
pendingList.push({ specId, title: spec?.title ?? specId, completedAt });
|
|
102
|
+
await writePendingReleaseLedger(pendingPath, pendingList);
|
|
103
|
+
});
|
|
67
104
|
// Auto-commit planu/ changes so pending.json is never left unstaged
|
|
68
105
|
void (async () => {
|
|
69
106
|
try {
|
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
async function handler(ctx) {
|
|
2
|
-
const { projectPath,
|
|
2
|
+
const { projectPath, projectId } = ctx;
|
|
3
3
|
if (!projectPath) {
|
|
4
4
|
return;
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
try {
|
|
7
|
+
const { specStore } = await import('../../../storage/index.js');
|
|
8
|
+
await specStore.withFreshSpecsLock(projectId, async (freshSpecs) => {
|
|
9
|
+
const approvedCount = freshSpecs.filter((spec) => spec.status === 'approved').length;
|
|
10
|
+
if (approvedCount < 2) {
|
|
11
|
+
const { writeOrchestrationPlan } = await import('../../../engine/next-spec-resolver/session-writer.js');
|
|
12
|
+
await writeOrchestrationPlan(projectPath, null);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
10
15
|
const { resolveOrchestrationPlan } = await import('../../../engine/next-spec-resolver/orchestration-planner.js');
|
|
11
16
|
const { writeOrchestrationPlan } = await import('../../../engine/next-spec-resolver/session-writer.js');
|
|
12
|
-
const plan = await resolveOrchestrationPlan(projectPath,
|
|
17
|
+
const plan = await resolveOrchestrationPlan(projectPath, freshSpecs);
|
|
13
18
|
await writeOrchestrationPlan(projectPath, plan);
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
/* best-effort — never throw */
|
|
17
|
-
}
|
|
19
|
+
});
|
|
18
20
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const { writeOrchestrationPlan } = await import('../../../engine/next-spec-resolver/session-writer.js');
|
|
23
|
-
await writeOrchestrationPlan(projectPath, null);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
/* best-effort — never throw */
|
|
27
|
-
}
|
|
21
|
+
catch {
|
|
22
|
+
/* best-effort — never throw */
|
|
28
23
|
}
|
|
29
24
|
}
|
|
30
25
|
export const orchestrationPlanHook = {
|
|
31
26
|
id: 'orchestration-plan',
|
|
32
|
-
triggers: ['approved'],
|
|
27
|
+
triggers: ['approved', 'done'],
|
|
33
28
|
requiresProjectPath: true,
|
|
34
|
-
label: 'Auto-generate/clear orchestration plan on approved
|
|
29
|
+
label: 'Auto-generate/clear orchestration plan on approved or done',
|
|
35
30
|
handler,
|
|
36
31
|
};
|
|
37
32
|
//# sourceMappingURL=orchestration-plan.hook.js.map
|
|
@@ -123,7 +123,11 @@ function buildNodeSetupSteps(ctx) {
|
|
|
123
123
|
const version = ctx.nodeVersion ?? '20';
|
|
124
124
|
const steps = [checkoutStep()];
|
|
125
125
|
if (ctx.packageManager === 'pnpm') {
|
|
126
|
-
steps.push({
|
|
126
|
+
steps.push({
|
|
127
|
+
name: 'Setup pnpm',
|
|
128
|
+
uses: 'pnpm/action-setup@v4.4.0',
|
|
129
|
+
with: { version: 'latest' },
|
|
130
|
+
});
|
|
127
131
|
}
|
|
128
132
|
steps.push({
|
|
129
133
|
name: 'Setup Node.js',
|
|
@@ -73,7 +73,9 @@ function formatBacklog(specs) {
|
|
|
73
73
|
export async function generateContextMd(projectPath, projectId) {
|
|
74
74
|
try {
|
|
75
75
|
const specs = await specStore.listSpecs(projectId);
|
|
76
|
-
const implementing = specs
|
|
76
|
+
const implementing = specs
|
|
77
|
+
.filter((s) => s.status === 'implementing')
|
|
78
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
77
79
|
const backlog = specs
|
|
78
80
|
.filter((s) => s.status === 'draft' || s.status === 'approved')
|
|
79
81
|
.sort((a, b) => b.estimation.devHours - a.estimation.devHours);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CriterionIdentity, CriterionIdentityOptions } from '../types/criterion-identity.js';
|
|
2
|
+
export type { CriterionIdentity, CriterionIdentityOptions } from '../types/criterion-identity.js';
|
|
3
|
+
export declare function normalizeCriterionText(value: string): string;
|
|
4
|
+
export declare function createCriterionIdentity(value: string, options?: CriterionIdentityOptions): CriterionIdentity;
|
|
5
|
+
export declare function criterionIdentitiesMatch(left: CriterionIdentity, right: CriterionIdentity): boolean;
|
|
6
|
+
//# sourceMappingURL=criterion-identity.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const CRITERION_ID_PREFIX = /^(AC|AB)\s*-?\s*(\d+)\b\s*[:.)-]?\s*/i;
|
|
3
|
+
export function normalizeCriterionText(value) {
|
|
4
|
+
return value
|
|
5
|
+
.normalize('NFKC')
|
|
6
|
+
.replace(/^[-*+]\s*\[[ xX]\]\s*/, '')
|
|
7
|
+
.replace(CRITERION_ID_PREFIX, '')
|
|
8
|
+
.replace(/[\p{P}]+/gu, ' ')
|
|
9
|
+
.replace(/[\s\u00a0]+/g, ' ')
|
|
10
|
+
.trim()
|
|
11
|
+
.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
export function createCriterionIdentity(value, options = {}) {
|
|
14
|
+
const stripped = value
|
|
15
|
+
.normalize('NFKC')
|
|
16
|
+
.replace(/^[-*+]\s*\[[ xX]\]\s*/, '')
|
|
17
|
+
.trim();
|
|
18
|
+
const explicit = CRITERION_ID_PREFIX.exec(stripped);
|
|
19
|
+
const fallbackOrdinal = (options.index ?? 0) + 1;
|
|
20
|
+
const kind = (explicit?.[1] ?? options.defaultKind ?? 'AC').toUpperCase();
|
|
21
|
+
const ordinal = String(Number.parseInt(explicit?.[2] ?? String(fallbackOrdinal), 10));
|
|
22
|
+
const id = `${kind}${ordinal}`;
|
|
23
|
+
const text = stripped.replace(CRITERION_ID_PREFIX, '').trim();
|
|
24
|
+
const normalizedText = normalizeCriterionText(text);
|
|
25
|
+
const aliases = new Set([
|
|
26
|
+
normalizeCriterionText(value),
|
|
27
|
+
normalizedText,
|
|
28
|
+
id.toLowerCase(),
|
|
29
|
+
`${kind.toLowerCase()}-${ordinal}`,
|
|
30
|
+
]);
|
|
31
|
+
if (!explicit && options.index !== undefined) {
|
|
32
|
+
aliases.add(`ac${String(options.index + 1)}`);
|
|
33
|
+
aliases.add(`ac-${String(options.index + 1)}`);
|
|
34
|
+
aliases.add(`ab${String(options.index + 1)}`);
|
|
35
|
+
aliases.add(`ab-${String(options.index + 1)}`);
|
|
36
|
+
}
|
|
37
|
+
const stableKey = createHash('sha256')
|
|
38
|
+
.update(id.toLowerCase())
|
|
39
|
+
.update('\0')
|
|
40
|
+
.update(normalizedText)
|
|
41
|
+
.digest('hex')
|
|
42
|
+
.slice(0, 16);
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
text,
|
|
46
|
+
normalizedText,
|
|
47
|
+
stableKey,
|
|
48
|
+
aliases: [...aliases].filter(Boolean).sort(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function criterionIdentitiesMatch(left, right) {
|
|
52
|
+
if (left.stableKey === right.stableKey) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (left.normalizedText.length > 0 &&
|
|
56
|
+
right.normalizedText.length > 0 &&
|
|
57
|
+
left.normalizedText !== right.normalizedText) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const rightAliases = new Set(right.aliases);
|
|
61
|
+
return left.aliases.some((alias) => rightAliases.has(alias));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=criterion-identity.js.map
|