@izkac/forgekit 0.3.11 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/bin/forge.mjs +107 -107
  2. package/bin/forgekit.mjs +83 -83
  3. package/bin/review.mjs +81 -81
  4. package/package.json +1 -1
  5. package/scripts/prepack.mjs +78 -78
  6. package/scripts/run-tests.mjs +50 -50
  7. package/src/adr.mjs +236 -236
  8. package/src/adr.test.mjs +170 -170
  9. package/src/change.mjs +327 -234
  10. package/src/change.test.mjs +145 -83
  11. package/src/cleanup-sessions.mjs +84 -84
  12. package/src/config.mjs +103 -103
  13. package/src/defer.mjs +75 -75
  14. package/src/doctor.mjs +350 -341
  15. package/src/doctor.test.mjs +114 -114
  16. package/src/init.mjs +680 -621
  17. package/src/install.mjs +815 -815
  18. package/src/install.test.mjs +180 -180
  19. package/src/integrity-check.mjs +60 -60
  20. package/src/integrity.mjs +682 -682
  21. package/src/integrity.test.mjs +566 -566
  22. package/src/lib.mjs +143 -143
  23. package/src/models.defaults.json +41 -41
  24. package/src/new-session.mjs +99 -99
  25. package/src/openspec-overlays/README.md +19 -19
  26. package/src/openspec-overlays/openspec-apply-change-footer.md +14 -14
  27. package/src/openspec-overlays/opsx-apply-completion-step.md +1 -1
  28. package/src/openspec-overlays/opsx-apply-implement-step.md +11 -11
  29. package/src/paths.mjs +92 -92
  30. package/src/plan-engine.mjs +321 -278
  31. package/src/plan-engine.test.mjs +447 -283
  32. package/src/preferences.defaults.json +78 -78
  33. package/src/preferences.mjs +438 -438
  34. package/src/preferences.test.mjs +174 -174
  35. package/src/record-evidence.mjs +204 -204
  36. package/src/resolve-model.mjs +312 -312
  37. package/src/resolve-model.test.mjs +194 -194
  38. package/src/review/cli.test.mjs +117 -117
  39. package/src/review/export.mjs +172 -172
  40. package/src/review/export.test.mjs +197 -197
  41. package/src/review/fixtures/valid-review.json +42 -42
  42. package/src/review/lib.mjs +894 -894
  43. package/src/review/lib.test.mjs +266 -266
  44. package/src/review/schema.json +196 -196
  45. package/src/review/signals.test.mjs +62 -62
  46. package/src/score-cli.mjs +68 -68
  47. package/src/score.mjs +568 -566
  48. package/src/score.test.mjs +366 -340
  49. package/src/session-reminder.mjs +207 -207
  50. package/src/session-status.mjs +70 -70
  51. package/src/set-models.mjs +186 -186
  52. package/src/set-phase.mjs +205 -205
  53. package/src/set-prefs.mjs +294 -294
  54. package/src/specs-sync.mjs +234 -0
  55. package/src/specs-sync.test.mjs +114 -0
  56. package/src/spine.mjs +93 -93
  57. package/src/triage-prompt.mjs +175 -175
  58. package/src/triage-prompt.test.mjs +50 -50
  59. package/src/vendor-openspec-overlays.mjs +176 -176
  60. package/src/vendor-openspec-overlays.test.mjs +62 -62
  61. package/vendor/skills/archive-to-adr/SKILL.md +149 -149
  62. package/vendor/skills/forge/SKILL.md +136 -136
  63. package/vendor/skills/forge/docs/forge.md +650 -647
  64. package/vendor/skills/forge/phases/brainstorm.md +23 -23
  65. package/vendor/skills/forge/phases/finish.md +90 -87
  66. package/vendor/skills/forge/phases/implement.md +77 -77
  67. package/vendor/skills/forge/phases/plan-openspec.md +60 -60
  68. package/vendor/skills/forge/phases/plan-specs.md +163 -117
  69. package/vendor/skills/forge/phases/review.md +25 -25
  70. package/vendor/skills/forge/phases/verify.md +124 -124
  71. package/vendor/skills/forge/references/forge-layout.md +85 -85
  72. package/vendor/skills/forge/references/pace.md +115 -115
  73. package/vendor/skills/forge/references/plan-routing.md +52 -51
  74. package/vendor/skills/forge/references/runtime-integrity.md +232 -225
  75. package/vendor/skills/forge/references/substantial-work.md +37 -37
  76. package/vendor/skills/forge/references/test-evidence.md +30 -30
  77. package/vendor/skills/forge/references/test-strategy.md +68 -68
  78. package/vendor/skills/forge/skills/subagent-driven-development/SKILL.md +87 -87
  79. package/vendor/skills/forge/subagents/final-reviewer-prompt.md +56 -56
  80. package/vendor/skills/forge/subagents/implementer-prompt.md +38 -38
  81. package/vendor/skills/git-resolve-adr-conflict/SKILL.md +132 -132
  82. package/vendor/skills/thorough-code-review/SKILL.md +290 -290
  83. package/vendor/skills/thorough-code-review/examples.md +133 -133
  84. package/vendor/skills/thorough-code-review/reference/accepted-risks.md +26 -26
  85. package/vendor/skills/thorough-code-review/reference/lenses.md +96 -96
  86. package/vendor/skills/thorough-code-review/reference/phase1-scout.md +62 -62
  87. package/vendor/skills/thorough-code-review/reference/phase2-skeptic.md +105 -105
  88. package/vendor/skills/thorough-code-review/reference/report-schema.json +222 -222
  89. package/vendor/skills/thorough-code-review/reference/report-template.md +115 -115
  90. package/vendor/skills/thorough-code-review/reference/severity-rubric.md +49 -49
  91. package/vendor/skills/thorough-code-review/reference/signals-preflight.md +55 -55
  92. package/vendor/templates/adr/README.md +7 -7
  93. package/vendor/templates/adr/decisions.md +141 -141
  94. package/vendor/templates/adr/hooks/check-pending-adrs.mjs +74 -74
  95. package/vendor/templates/adr/hooks/check-pending-adrs.sh +3 -3
  96. package/vendor/templates/adr/hooks/openspec-archive-agent-message.mjs +52 -52
  97. package/vendor/templates/adr/hooks/openspec-archive-agent-message.sh +3 -3
  98. package/vendor/templates/project/claude/commands/forge-apply.md +75 -75
  99. package/vendor/templates/project/claude/commands/forge-brainstorm.md +7 -7
  100. package/vendor/templates/project/claude/commands/forge-build.md +17 -17
  101. package/vendor/templates/project/claude/commands/forge-plan.md +12 -12
  102. package/vendor/templates/project/claude/commands/forge-skip.md +14 -14
  103. package/vendor/templates/project/claude/commands/forge-status.md +16 -16
  104. package/vendor/templates/project/claude/commands/forge.md +16 -16
  105. package/vendor/templates/project/claude/hooks/forge-prompt-hook.mjs +73 -73
  106. package/vendor/templates/project/claude/hooks/forge-session-start.mjs +19 -19
  107. package/vendor/templates/project/claude/hooks/forge-triage-hook.mjs +77 -77
  108. package/vendor/templates/project/cursor/commands/forge-apply.md +75 -75
  109. package/vendor/templates/project/cursor/commands/forge-brainstorm.md +10 -10
  110. package/vendor/templates/project/cursor/commands/forge-build.md +17 -17
  111. package/vendor/templates/project/cursor/commands/forge-plan.md +15 -15
  112. package/vendor/templates/project/cursor/commands/forge-skip.md +14 -14
  113. package/vendor/templates/project/cursor/commands/forge-status.md +16 -16
  114. package/vendor/templates/project/cursor/commands/forge.md +16 -16
  115. package/vendor/templates/project/cursor/hooks/forge-session-start.mjs +30 -30
  116. package/vendor/templates/project/cursor/hooks/forge-session-start.sh +3 -3
@@ -1,196 +1,196 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://forgekit.local/schemas/code-review-report.json",
4
- "title": "ThoroughCodeReviewReport",
5
- "type": "object",
6
- "required": ["review_id", "kind", "created_at", "scope", "lenses", "summary", "findings"],
7
- "additionalProperties": false,
8
- "properties": {
9
- "review_id": { "type": "string", "minLength": 1 },
10
- "kind": { "type": "string", "enum": ["review", "reverify"] },
11
- "created_at": { "type": "string", "format": "date-time" },
12
- "scope": {
13
- "type": "object",
14
- "required": ["type", "description"],
15
- "additionalProperties": false,
16
- "properties": {
17
- "type": {
18
- "type": "string",
19
- "enum": ["uncommitted", "branch", "paths", "commit_range", "file"]
20
- },
21
- "description": { "type": "string", "minLength": 1 },
22
- "base_sha": { "type": "string" },
23
- "head_sha": { "type": "string" },
24
- "paths": { "type": "array", "items": { "type": "string" } }
25
- }
26
- },
27
- "lenses": {
28
- "type": "array",
29
- "minItems": 1,
30
- "items": {
31
- "type": "string",
32
- "enum": [
33
- "security",
34
- "correctness",
35
- "smells",
36
- "architecture",
37
- "performance",
38
- "tests",
39
- "contracts",
40
- "errors",
41
- "maintainability"
42
- ]
43
- }
44
- },
45
- "parent_report": { "type": "string" },
46
- "summary": {
47
- "type": "object",
48
- "required": ["tentative_count"],
49
- "additionalProperties": true,
50
- "properties": {
51
- "tentative_count": { "type": "integer", "minimum": 0 },
52
- "confirmed": { "type": "integer", "minimum": 0 },
53
- "downgraded": { "type": "integer", "minimum": 0 },
54
- "false_positive": { "type": "integer", "minimum": 0 },
55
- "needs_decision": { "type": "integer", "minimum": 0 },
56
- "resolved": { "type": "integer", "minimum": 0 },
57
- "still_open": { "type": "integer", "minimum": 0 },
58
- "partially_fixed": { "type": "integer", "minimum": 0 },
59
- "regressed": { "type": "integer", "minimum": 0 }
60
- }
61
- },
62
- "findings": {
63
- "type": "array",
64
- "items": { "$ref": "#/$defs/finding" }
65
- },
66
- "dedupe_preflight": {
67
- "type": "object",
68
- "additionalProperties": false,
69
- "properties": {
70
- "count": { "type": "integer", "minimum": 0 },
71
- "items": {
72
- "type": "array",
73
- "items": {
74
- "type": "object",
75
- "required": ["id", "location", "claim"],
76
- "properties": {
77
- "id": { "type": "string" },
78
- "location": { "type": "string" },
79
- "claim": { "type": "string" }
80
- }
81
- }
82
- }
83
- }
84
- },
85
- "coverage": {
86
- "type": "object",
87
- "description": "Recall / completeness pass ledger.",
88
- "additionalProperties": true,
89
- "properties": {
90
- "files_reviewed": { "type": "array", "items": { "type": "string" } },
91
- "files_skipped": { "type": "array", "items": { "type": "string" } },
92
- "lenses_without_findings": {
93
- "type": "array",
94
- "items": {
95
- "type": "object",
96
- "required": ["lens", "reason"],
97
- "properties": {
98
- "lens": {
99
- "type": "string",
100
- "enum": [
101
- "security",
102
- "correctness",
103
- "smells",
104
- "architecture",
105
- "performance",
106
- "tests",
107
- "contracts",
108
- "errors",
109
- "maintainability"
110
- ]
111
- },
112
- "reason": { "type": "string", "minLength": 1 }
113
- }
114
- }
115
- }
116
- }
117
- },
118
- "signals": {
119
- "type": "object",
120
- "description": "Signals pre-flight: deterministic tools run before the scout.",
121
- "additionalProperties": true,
122
- "properties": {
123
- "tools": {
124
- "type": "array",
125
- "items": {
126
- "type": "object",
127
- "required": ["name"],
128
- "properties": {
129
- "name": { "type": "string", "minLength": 1 },
130
- "status": { "type": "string", "enum": ["pass", "fail", "skipped"] },
131
- "summary": { "type": "string" }
132
- }
133
- }
134
- }
135
- }
136
- },
137
- "stats": {
138
- "type": "object",
139
- "description": "Pipeline stats: subagent counts and shortcut tallies for the run.",
140
- "additionalProperties": false,
141
- "properties": {
142
- "scouts": { "type": "integer", "minimum": 0 },
143
- "skeptics_dedicated": { "type": "integer", "minimum": 0 },
144
- "skeptics_batched": { "type": "integer", "minimum": 0 },
145
- "inline_verdicts": { "type": "integer", "minimum": 0 },
146
- "grounded_skips": { "type": "integer", "minimum": 0 },
147
- "carried_forward": { "type": "integer", "minimum": 0 },
148
- "second_opinions": { "type": "integer", "minimum": 0 }
149
- }
150
- }
151
- },
152
- "$defs": {
153
- "finding": {
154
- "type": "object",
155
- "required": ["id", "lens", "location", "claim", "severity", "verdict", "verdict_reason"],
156
- "additionalProperties": false,
157
- "properties": {
158
- "id": { "type": "string", "pattern": "^(F|dup)-[0-9]{3}$" },
159
- "lens": {
160
- "type": "string",
161
- "enum": [
162
- "security",
163
- "correctness",
164
- "smells",
165
- "architecture",
166
- "performance",
167
- "tests",
168
- "contracts",
169
- "errors",
170
- "maintainability"
171
- ]
172
- },
173
- "location": { "type": "string", "minLength": 1 },
174
- "claim": { "type": "string", "minLength": 1 },
175
- "evidence": { "type": "string" },
176
- "severity": { "type": "string", "enum": ["critical", "important", "minor"] },
177
- "phase1_confidence": { "type": "string", "enum": ["low", "medium", "high"] },
178
- "original_severity": { "type": "string", "enum": ["critical", "important", "minor"] },
179
- "verdict": {
180
- "type": "string",
181
- "enum": [
182
- "confirmed",
183
- "false_positive",
184
- "downgraded",
185
- "needs_decision",
186
- "resolved",
187
- "still_open",
188
- "partially_fixed",
189
- "regressed"
190
- ]
191
- },
192
- "verdict_reason": { "type": "string", "minLength": 1 }
193
- }
194
- }
195
- }
196
- }
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://forgekit.local/schemas/code-review-report.json",
4
+ "title": "ThoroughCodeReviewReport",
5
+ "type": "object",
6
+ "required": ["review_id", "kind", "created_at", "scope", "lenses", "summary", "findings"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "review_id": { "type": "string", "minLength": 1 },
10
+ "kind": { "type": "string", "enum": ["review", "reverify"] },
11
+ "created_at": { "type": "string", "format": "date-time" },
12
+ "scope": {
13
+ "type": "object",
14
+ "required": ["type", "description"],
15
+ "additionalProperties": false,
16
+ "properties": {
17
+ "type": {
18
+ "type": "string",
19
+ "enum": ["uncommitted", "branch", "paths", "commit_range", "file"]
20
+ },
21
+ "description": { "type": "string", "minLength": 1 },
22
+ "base_sha": { "type": "string" },
23
+ "head_sha": { "type": "string" },
24
+ "paths": { "type": "array", "items": { "type": "string" } }
25
+ }
26
+ },
27
+ "lenses": {
28
+ "type": "array",
29
+ "minItems": 1,
30
+ "items": {
31
+ "type": "string",
32
+ "enum": [
33
+ "security",
34
+ "correctness",
35
+ "smells",
36
+ "architecture",
37
+ "performance",
38
+ "tests",
39
+ "contracts",
40
+ "errors",
41
+ "maintainability"
42
+ ]
43
+ }
44
+ },
45
+ "parent_report": { "type": "string" },
46
+ "summary": {
47
+ "type": "object",
48
+ "required": ["tentative_count"],
49
+ "additionalProperties": true,
50
+ "properties": {
51
+ "tentative_count": { "type": "integer", "minimum": 0 },
52
+ "confirmed": { "type": "integer", "minimum": 0 },
53
+ "downgraded": { "type": "integer", "minimum": 0 },
54
+ "false_positive": { "type": "integer", "minimum": 0 },
55
+ "needs_decision": { "type": "integer", "minimum": 0 },
56
+ "resolved": { "type": "integer", "minimum": 0 },
57
+ "still_open": { "type": "integer", "minimum": 0 },
58
+ "partially_fixed": { "type": "integer", "minimum": 0 },
59
+ "regressed": { "type": "integer", "minimum": 0 }
60
+ }
61
+ },
62
+ "findings": {
63
+ "type": "array",
64
+ "items": { "$ref": "#/$defs/finding" }
65
+ },
66
+ "dedupe_preflight": {
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "properties": {
70
+ "count": { "type": "integer", "minimum": 0 },
71
+ "items": {
72
+ "type": "array",
73
+ "items": {
74
+ "type": "object",
75
+ "required": ["id", "location", "claim"],
76
+ "properties": {
77
+ "id": { "type": "string" },
78
+ "location": { "type": "string" },
79
+ "claim": { "type": "string" }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ },
85
+ "coverage": {
86
+ "type": "object",
87
+ "description": "Recall / completeness pass ledger.",
88
+ "additionalProperties": true,
89
+ "properties": {
90
+ "files_reviewed": { "type": "array", "items": { "type": "string" } },
91
+ "files_skipped": { "type": "array", "items": { "type": "string" } },
92
+ "lenses_without_findings": {
93
+ "type": "array",
94
+ "items": {
95
+ "type": "object",
96
+ "required": ["lens", "reason"],
97
+ "properties": {
98
+ "lens": {
99
+ "type": "string",
100
+ "enum": [
101
+ "security",
102
+ "correctness",
103
+ "smells",
104
+ "architecture",
105
+ "performance",
106
+ "tests",
107
+ "contracts",
108
+ "errors",
109
+ "maintainability"
110
+ ]
111
+ },
112
+ "reason": { "type": "string", "minLength": 1 }
113
+ }
114
+ }
115
+ }
116
+ }
117
+ },
118
+ "signals": {
119
+ "type": "object",
120
+ "description": "Signals pre-flight: deterministic tools run before the scout.",
121
+ "additionalProperties": true,
122
+ "properties": {
123
+ "tools": {
124
+ "type": "array",
125
+ "items": {
126
+ "type": "object",
127
+ "required": ["name"],
128
+ "properties": {
129
+ "name": { "type": "string", "minLength": 1 },
130
+ "status": { "type": "string", "enum": ["pass", "fail", "skipped"] },
131
+ "summary": { "type": "string" }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ },
137
+ "stats": {
138
+ "type": "object",
139
+ "description": "Pipeline stats: subagent counts and shortcut tallies for the run.",
140
+ "additionalProperties": false,
141
+ "properties": {
142
+ "scouts": { "type": "integer", "minimum": 0 },
143
+ "skeptics_dedicated": { "type": "integer", "minimum": 0 },
144
+ "skeptics_batched": { "type": "integer", "minimum": 0 },
145
+ "inline_verdicts": { "type": "integer", "minimum": 0 },
146
+ "grounded_skips": { "type": "integer", "minimum": 0 },
147
+ "carried_forward": { "type": "integer", "minimum": 0 },
148
+ "second_opinions": { "type": "integer", "minimum": 0 }
149
+ }
150
+ }
151
+ },
152
+ "$defs": {
153
+ "finding": {
154
+ "type": "object",
155
+ "required": ["id", "lens", "location", "claim", "severity", "verdict", "verdict_reason"],
156
+ "additionalProperties": false,
157
+ "properties": {
158
+ "id": { "type": "string", "pattern": "^(F|dup)-[0-9]{3}$" },
159
+ "lens": {
160
+ "type": "string",
161
+ "enum": [
162
+ "security",
163
+ "correctness",
164
+ "smells",
165
+ "architecture",
166
+ "performance",
167
+ "tests",
168
+ "contracts",
169
+ "errors",
170
+ "maintainability"
171
+ ]
172
+ },
173
+ "location": { "type": "string", "minLength": 1 },
174
+ "claim": { "type": "string", "minLength": 1 },
175
+ "evidence": { "type": "string" },
176
+ "severity": { "type": "string", "enum": ["critical", "important", "minor"] },
177
+ "phase1_confidence": { "type": "string", "enum": ["low", "medium", "high"] },
178
+ "original_severity": { "type": "string", "enum": ["critical", "important", "minor"] },
179
+ "verdict": {
180
+ "type": "string",
181
+ "enum": [
182
+ "confirmed",
183
+ "false_positive",
184
+ "downgraded",
185
+ "needs_decision",
186
+ "resolved",
187
+ "still_open",
188
+ "partially_fixed",
189
+ "regressed"
190
+ ]
191
+ },
192
+ "verdict_reason": { "type": "string", "minLength": 1 }
193
+ }
194
+ }
195
+ }
196
+ }
@@ -1,62 +1,62 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mapPathsToWorkspaces, suggestSignalCommands } from './lib.mjs';
4
- import { parseArgs, runSignals } from './signals.mjs';
5
-
6
- const PACKAGES = [
7
- { name: '@janus/persona', dir: 'services/persona', scripts: { typecheck: 'tsc', test: 'vitest' } },
8
- { name: '@janus/mercury', dir: 'services/mercury', scripts: { test: 'vitest' } },
9
- { name: '@janus/persona-contract', dir: 'packages/persona-contract', scripts: { build: 'tsc' } },
10
- ];
11
-
12
- test('mapPathsToWorkspaces matches the deepest owning workspace', () => {
13
- const { workspaces, unmatched } = mapPathsToWorkspaces(
14
- [
15
- 'services/persona/src/routes/profile.ts',
16
- 'services/mercury/src/index.ts',
17
- 'docs/thorough-code-review.md',
18
- ],
19
- PACKAGES,
20
- );
21
- assert.deepEqual(
22
- workspaces.map((w) => w.name),
23
- ['@janus/mercury', '@janus/persona'],
24
- );
25
- assert.deepEqual(unmatched, ['docs/thorough-code-review.md']);
26
- });
27
-
28
- test('mapPathsToWorkspaces normalizes backslashes and dedupes', () => {
29
- const { workspaces } = mapPathsToWorkspaces(
30
- ['services\\persona\\a.ts', 'services\\persona\\b.ts'],
31
- PACKAGES,
32
- );
33
- assert.equal(workspaces.length, 1);
34
- assert.equal(workspaces[0].name, '@janus/persona');
35
- });
36
-
37
- test('suggestSignalCommands emits only scripts that exist, in run order', () => {
38
- const commands = suggestSignalCommands([
39
- { name: '@janus/persona', scripts: { typecheck: 'tsc', test: 'vitest' } },
40
- { name: '@janus/mercury', scripts: { test: 'vitest' } },
41
- ]);
42
- assert.deepEqual(commands, [
43
- 'npm run typecheck -w @janus/persona',
44
- 'npm run test -w @janus/persona',
45
- 'npm run test -w @janus/mercury',
46
- ]);
47
- });
48
-
49
- test('runSignals plans commands and flags contract touches', () => {
50
- const opts = parseArgs(['--paths', 'services/persona/src/routes/profile.ts', '--json']);
51
- const result = runSignals(opts, process.cwd(), PACKAGES);
52
- assert.equal(result.exitCode, 0);
53
- assert.deepEqual(result.plan.workspaces, ['@janus/persona']);
54
- assert.ok(result.plan.commands.includes('npm run typecheck -w @janus/persona'));
55
- assert.ok(result.plan.notes.some((n) => n.includes('route-parity')));
56
- });
57
-
58
- test('runSignals reports paths outside any workspace', () => {
59
- const opts = parseArgs(['--paths', 'scripts/review/lib.mjs']);
60
- const result = runSignals(opts, process.cwd(), PACKAGES);
61
- assert.ok(result.plan.notes.some((n) => n.includes('outside any workspace')));
62
- });
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mapPathsToWorkspaces, suggestSignalCommands } from './lib.mjs';
4
+ import { parseArgs, runSignals } from './signals.mjs';
5
+
6
+ const PACKAGES = [
7
+ { name: '@janus/persona', dir: 'services/persona', scripts: { typecheck: 'tsc', test: 'vitest' } },
8
+ { name: '@janus/mercury', dir: 'services/mercury', scripts: { test: 'vitest' } },
9
+ { name: '@janus/persona-contract', dir: 'packages/persona-contract', scripts: { build: 'tsc' } },
10
+ ];
11
+
12
+ test('mapPathsToWorkspaces matches the deepest owning workspace', () => {
13
+ const { workspaces, unmatched } = mapPathsToWorkspaces(
14
+ [
15
+ 'services/persona/src/routes/profile.ts',
16
+ 'services/mercury/src/index.ts',
17
+ 'docs/thorough-code-review.md',
18
+ ],
19
+ PACKAGES,
20
+ );
21
+ assert.deepEqual(
22
+ workspaces.map((w) => w.name),
23
+ ['@janus/mercury', '@janus/persona'],
24
+ );
25
+ assert.deepEqual(unmatched, ['docs/thorough-code-review.md']);
26
+ });
27
+
28
+ test('mapPathsToWorkspaces normalizes backslashes and dedupes', () => {
29
+ const { workspaces } = mapPathsToWorkspaces(
30
+ ['services\\persona\\a.ts', 'services\\persona\\b.ts'],
31
+ PACKAGES,
32
+ );
33
+ assert.equal(workspaces.length, 1);
34
+ assert.equal(workspaces[0].name, '@janus/persona');
35
+ });
36
+
37
+ test('suggestSignalCommands emits only scripts that exist, in run order', () => {
38
+ const commands = suggestSignalCommands([
39
+ { name: '@janus/persona', scripts: { typecheck: 'tsc', test: 'vitest' } },
40
+ { name: '@janus/mercury', scripts: { test: 'vitest' } },
41
+ ]);
42
+ assert.deepEqual(commands, [
43
+ 'npm run typecheck -w @janus/persona',
44
+ 'npm run test -w @janus/persona',
45
+ 'npm run test -w @janus/mercury',
46
+ ]);
47
+ });
48
+
49
+ test('runSignals plans commands and flags contract touches', () => {
50
+ const opts = parseArgs(['--paths', 'services/persona/src/routes/profile.ts', '--json']);
51
+ const result = runSignals(opts, process.cwd(), PACKAGES);
52
+ assert.equal(result.exitCode, 0);
53
+ assert.deepEqual(result.plan.workspaces, ['@janus/persona']);
54
+ assert.ok(result.plan.commands.includes('npm run typecheck -w @janus/persona'));
55
+ assert.ok(result.plan.notes.some((n) => n.includes('route-parity')));
56
+ });
57
+
58
+ test('runSignals reports paths outside any workspace', () => {
59
+ const opts = parseArgs(['--paths', 'scripts/review/lib.mjs']);
60
+ const result = runSignals(opts, process.cwd(), PACKAGES);
61
+ assert.ok(result.plan.notes.some((n) => n.includes('outside any workspace')));
62
+ });
package/src/score-cli.mjs CHANGED
@@ -1,68 +1,68 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Score the active Forge session (L2 scorecard).
4
- *
5
- * Usage:
6
- * forge score [--session <id>] [--write] [--json]
7
- *
8
- * Without --write: print JSON to stdout.
9
- * With --write: also write scorecard.json + scorecard.md into the session dir.
10
- */
11
-
12
- import { loadSession, readActive } from './lib.mjs';
13
- import { formatScorecardMarkdown, scoreSession, writeSessionScorecard } from './score.mjs';
14
-
15
- const args = process.argv.slice(2);
16
- if (args[0] === '--help') {
17
- process.stdout.write(
18
- 'Usage: forge score [--session <id>] [--write] [--json]\n' +
19
- ' Scores session artifacts (spine, deferrals, product-loop, evidence, pace).\n' +
20
- ' --write save scorecard.json + scorecard.md into the session dir\n' +
21
- ' --json print JSON (default); omit for markdown summary on stdout\n',
22
- );
23
- process.exit(0);
24
- }
25
-
26
- let sessionId = null;
27
- let write = false;
28
- let asJson = true;
29
- for (let i = 0; i < args.length; i += 1) {
30
- if (args[i] === '--session' && args[i + 1]) {
31
- sessionId = args[i + 1];
32
- i += 1;
33
- } else if (args[i] === '--write') {
34
- write = true;
35
- } else if (args[i] === '--markdown' || args[i] === '--md') {
36
- asJson = false;
37
- } else if (args[i] === '--json') {
38
- asJson = true;
39
- }
40
- }
41
-
42
- if (!sessionId) {
43
- const active = readActive();
44
- sessionId = active?.sessionId;
45
- }
46
- if (!sessionId) {
47
- process.stderr.write('No active session. Run forge new first.\n');
48
- process.exit(1);
49
- }
50
-
51
- const { dir, session } = loadSession(sessionId);
52
- const result = write
53
- ? writeSessionScorecard({ sessionDir: dir, session })
54
- : { card: scoreSession({ sessionDir: dir, session }), jsonPath: null, mdPath: null };
55
-
56
- const { card } = result;
57
- if (asJson) {
58
- process.stdout.write(`${JSON.stringify(card, null, 2)}\n`);
59
- } else {
60
- process.stdout.write(formatScorecardMarkdown(card));
61
- }
62
-
63
- if (write && result.mdPath) {
64
- process.stderr.write(`Wrote ${result.mdPath}\n`);
65
- }
66
-
67
- // Non-zero if grade is D/F — useful in CI / finish hooks
68
- process.exit(card.grade === 'D' || card.grade === 'F' ? 1 : 0);
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Score the active Forge session (L2 scorecard).
4
+ *
5
+ * Usage:
6
+ * forge score [--session <id>] [--write] [--json]
7
+ *
8
+ * Without --write: print JSON to stdout.
9
+ * With --write: also write scorecard.json + scorecard.md into the session dir.
10
+ */
11
+
12
+ import { loadSession, readActive } from './lib.mjs';
13
+ import { formatScorecardMarkdown, scoreSession, writeSessionScorecard } from './score.mjs';
14
+
15
+ const args = process.argv.slice(2);
16
+ if (args[0] === '--help') {
17
+ process.stdout.write(
18
+ 'Usage: forge score [--session <id>] [--write] [--json]\n' +
19
+ ' Scores session artifacts (spine, deferrals, product-loop, evidence, pace).\n' +
20
+ ' --write save scorecard.json + scorecard.md into the session dir\n' +
21
+ ' --json print JSON (default); omit for markdown summary on stdout\n',
22
+ );
23
+ process.exit(0);
24
+ }
25
+
26
+ let sessionId = null;
27
+ let write = false;
28
+ let asJson = true;
29
+ for (let i = 0; i < args.length; i += 1) {
30
+ if (args[i] === '--session' && args[i + 1]) {
31
+ sessionId = args[i + 1];
32
+ i += 1;
33
+ } else if (args[i] === '--write') {
34
+ write = true;
35
+ } else if (args[i] === '--markdown' || args[i] === '--md') {
36
+ asJson = false;
37
+ } else if (args[i] === '--json') {
38
+ asJson = true;
39
+ }
40
+ }
41
+
42
+ if (!sessionId) {
43
+ const active = readActive();
44
+ sessionId = active?.sessionId;
45
+ }
46
+ if (!sessionId) {
47
+ process.stderr.write('No active session. Run forge new first.\n');
48
+ process.exit(1);
49
+ }
50
+
51
+ const { dir, session } = loadSession(sessionId);
52
+ const result = write
53
+ ? writeSessionScorecard({ sessionDir: dir, session })
54
+ : { card: scoreSession({ sessionDir: dir, session }), jsonPath: null, mdPath: null };
55
+
56
+ const { card } = result;
57
+ if (asJson) {
58
+ process.stdout.write(`${JSON.stringify(card, null, 2)}\n`);
59
+ } else {
60
+ process.stdout.write(formatScorecardMarkdown(card));
61
+ }
62
+
63
+ if (write && result.mdPath) {
64
+ process.stderr.write(`Wrote ${result.mdPath}\n`);
65
+ }
66
+
67
+ // Non-zero if grade is D/F — useful in CI / finish hooks
68
+ process.exit(card.grade === 'D' || card.grade === 'F' ? 1 : 0);