@entelligentsia/forgecli 0.10.0 → 0.11.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 +95 -0
- package/README.md +21 -3
- package/dist/CHANGELOG-forge-plugin.md +90 -0
- package/dist/bin/config.js +6 -0
- package/dist/bin/config.js.map +1 -1
- package/dist/extensions/forgecli/add-pipeline.d.ts +19 -0
- package/dist/extensions/forgecli/add-pipeline.js +143 -0
- package/dist/extensions/forgecli/add-pipeline.js.map +1 -0
- package/dist/extensions/forgecli/add-task.d.ts +20 -0
- package/dist/extensions/forgecli/add-task.js +154 -0
- package/dist/extensions/forgecli/add-task.js.map +1 -0
- package/dist/extensions/forgecli/calibrate.d.ts +61 -0
- package/dist/extensions/forgecli/calibrate.js +488 -0
- package/dist/extensions/forgecli/calibrate.js.map +1 -0
- package/dist/extensions/forgecli/fix-bug.d.ts +9 -1
- package/dist/extensions/forgecli/fix-bug.js +155 -45
- package/dist/extensions/forgecli/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/forge-commands.js +15 -22
- package/dist/extensions/forgecli/forge-commands.js.map +1 -1
- package/dist/extensions/forgecli/forge-subagent.d.ts +16 -1
- package/dist/extensions/forgecli/forge-subagent.js +45 -8
- package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
- package/dist/extensions/forgecli/forge-update-command.d.ts +9 -0
- package/dist/extensions/forgecli/forge-update-command.js +106 -7
- package/dist/extensions/forgecli/forge-update-command.js.map +1 -1
- package/dist/extensions/forgecli/health-check.d.ts +22 -1
- package/dist/extensions/forgecli/health-check.js +177 -4
- package/dist/extensions/forgecli/health-check.js.map +1 -1
- package/dist/extensions/forgecli/hook-dispatcher.d.ts +25 -1
- package/dist/extensions/forgecli/hook-dispatcher.js +104 -9
- package/dist/extensions/forgecli/hook-dispatcher.js.map +1 -1
- package/dist/extensions/forgecli/hooks/check-update.d.ts +81 -0
- package/dist/extensions/forgecli/hooks/check-update.js +308 -0
- package/dist/extensions/forgecli/hooks/check-update.js.map +1 -0
- package/dist/extensions/forgecli/hooks/forge-permissions.d.ts +32 -0
- package/dist/extensions/forgecli/hooks/forge-permissions.js +119 -0
- package/dist/extensions/forgecli/hooks/forge-permissions.js.map +1 -0
- package/dist/extensions/forgecli/hooks/triage-error.d.ts +23 -0
- package/dist/extensions/forgecli/hooks/triage-error.js +62 -0
- package/dist/extensions/forgecli/hooks/triage-error.js.map +1 -0
- package/dist/extensions/forgecli/hooks/write-guard.d.ts +28 -0
- package/dist/extensions/forgecli/hooks/write-guard.js +225 -0
- package/dist/extensions/forgecli/hooks/write-guard.js.map +1 -0
- package/dist/extensions/forgecli/index.js +60 -0
- package/dist/extensions/forgecli/index.js.map +1 -1
- package/dist/extensions/forgecli/init-context.d.ts +1 -1
- package/dist/extensions/forgecli/init-context.js +21 -6
- package/dist/extensions/forgecli/init-context.js.map +1 -1
- package/dist/extensions/forgecli/materialize.d.ts +16 -0
- package/dist/extensions/forgecli/materialize.js +195 -0
- package/dist/extensions/forgecli/materialize.js.map +1 -0
- package/dist/extensions/forgecli/migrate.d.ts +19 -0
- package/dist/extensions/forgecli/migrate.js +258 -0
- package/dist/extensions/forgecli/migrate.js.map +1 -0
- package/dist/extensions/forgecli/migration-engine.d.ts +111 -0
- package/dist/extensions/forgecli/migration-engine.js +533 -0
- package/dist/extensions/forgecli/migration-engine.js.map +1 -0
- package/dist/extensions/forgecli/quiz-agent.d.ts +17 -0
- package/dist/extensions/forgecli/quiz-agent.js +98 -0
- package/dist/extensions/forgecli/quiz-agent.js.map +1 -0
- package/dist/extensions/forgecli/remove-command.d.ts +17 -0
- package/dist/extensions/forgecli/remove-command.js +124 -0
- package/dist/extensions/forgecli/remove-command.js.map +1 -0
- package/dist/extensions/forgecli/report-bug.d.ts +25 -0
- package/dist/extensions/forgecli/report-bug.js +159 -0
- package/dist/extensions/forgecli/report-bug.js.map +1 -0
- package/dist/extensions/forgecli/retrospective.d.ts +19 -0
- package/dist/extensions/forgecli/retrospective.js +156 -0
- package/dist/extensions/forgecli/retrospective.js.map +1 -0
- package/dist/extensions/forgecli/run-sprint.js +36 -3
- package/dist/extensions/forgecli/run-sprint.js.map +1 -1
- package/dist/extensions/forgecli/run-task.d.ts +9 -1
- package/dist/extensions/forgecli/run-task.js +66 -13
- package/dist/extensions/forgecli/run-task.js.map +1 -1
- package/dist/extensions/forgecli/session-registry.d.ts +40 -2
- package/dist/extensions/forgecli/session-registry.js +71 -1
- package/dist/extensions/forgecli/session-registry.js.map +1 -1
- package/dist/extensions/forgecli/status-command.d.ts +19 -0
- package/dist/extensions/forgecli/status-command.js +140 -0
- package/dist/extensions/forgecli/status-command.js.map +1 -0
- package/dist/extensions/forgecli/store-query.d.ts +22 -0
- package/dist/extensions/forgecli/store-query.js +107 -0
- package/dist/extensions/forgecli/store-query.js.map +1 -0
- package/dist/extensions/forgecli/store-repair.d.ts +17 -0
- package/dist/extensions/forgecli/store-repair.js +123 -0
- package/dist/extensions/forgecli/store-repair.js.map +1 -0
- package/dist/extensions/forgecli/test-orchestrate.js +1 -0
- package/dist/extensions/forgecli/test-orchestrate.js.map +1 -1
- package/dist/extensions/forgecli/thread-switcher.js +286 -41
- package/dist/extensions/forgecli/thread-switcher.js.map +1 -1
- package/dist/extensions/forgecli/transition-guard.js +7 -2
- package/dist/extensions/forgecli/transition-guard.js.map +1 -1
- package/dist/extensions/forgecli/update-tools.d.ts +23 -0
- package/dist/extensions/forgecli/update-tools.js +136 -0
- package/dist/extensions/forgecli/update-tools.js.map +1 -0
- package/dist/extensions/forgecli/viewport-events.js +10 -0
- package/dist/extensions/forgecli/viewport-events.js.map +1 -1
- package/dist/extensions/forgecli/viewport-renderer.d.ts +18 -0
- package/dist/extensions/forgecli/viewport-renderer.js +27 -0
- package/dist/extensions/forgecli/viewport-renderer.js.map +1 -1
- package/dist/extensions/forgecli/viewport-theme.js +4 -0
- package/dist/extensions/forgecli/viewport-theme.js.map +1 -1
- package/dist/extensions/forgecli/whats-new-widget.d.ts +13 -8
- package/dist/extensions/forgecli/whats-new-widget.js +111 -42
- package/dist/extensions/forgecli/whats-new-widget.js.map +1 -1
- package/dist/forge-payload/.base-pack/workflows/architect_approve.md +29 -3
- package/dist/forge-payload/.base-pack/workflows/commit_task.md +15 -8
- package/dist/forge-payload/.base-pack/workflows/fix_bug.md +327 -185
- package/dist/forge-payload/.base-pack/workflows/implement_plan.md +18 -10
- package/dist/forge-payload/.base-pack/workflows/plan_task.md +15 -9
- package/dist/forge-payload/.base-pack/workflows/review_code.md +14 -6
- package/dist/forge-payload/.base-pack/workflows/review_plan.md +18 -10
- package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
- package/dist/forge-payload/.schemas/bug.schema.json +3 -2
- package/dist/forge-payload/.schemas/config.schema.json +83 -0
- package/dist/forge-payload/.schemas/migrations.json +2049 -0
- package/dist/forge-payload/commands/regenerate.md +17 -1
- package/dist/forge-payload/meta/personas/README.md +16 -0
- package/dist/forge-payload/meta/personas/meta-architect.md +70 -0
- package/dist/forge-payload/meta/personas/meta-bug-fixer.md +73 -0
- package/dist/forge-payload/meta/personas/meta-collator.md +72 -0
- package/dist/forge-payload/meta/personas/meta-engineer.md +70 -0
- package/dist/forge-payload/meta/personas/meta-orchestrator.md +71 -0
- package/dist/forge-payload/meta/personas/meta-product-manager.md +82 -0
- package/dist/forge-payload/meta/personas/meta-qa-engineer.md +91 -0
- package/dist/forge-payload/meta/personas/meta-supervisor.md +92 -0
- package/dist/forge-payload/meta/skill-recommendations.md +154 -0
- package/dist/forge-payload/meta/skills/meta-architect-skills.md +43 -0
- package/dist/forge-payload/meta/skills/meta-bug-fixer-skills.md +43 -0
- package/dist/forge-payload/meta/skills/meta-collator-skills.md +41 -0
- package/dist/forge-payload/meta/skills/meta-engineer-skills.md +43 -0
- package/dist/forge-payload/meta/skills/meta-generic-skills.md +58 -0
- package/dist/forge-payload/meta/skills/meta-qa-engineer-skills.md +46 -0
- package/dist/forge-payload/meta/skills/meta-supervisor-skills.md +43 -0
- package/dist/forge-payload/meta/store-schema/bug.schema.md +71 -0
- package/dist/forge-payload/meta/store-schema/event.schema.md +76 -0
- package/dist/forge-payload/meta/store-schema/feature.schema.md +65 -0
- package/dist/forge-payload/meta/store-schema/sprint.schema.md +64 -0
- package/dist/forge-payload/meta/store-schema/task.schema.md +78 -0
- package/dist/forge-payload/meta/templates/meta-code-review.md +26 -0
- package/dist/forge-payload/meta/templates/meta-plan-review.md +28 -0
- package/dist/forge-payload/meta/templates/meta-plan.md +28 -0
- package/dist/forge-payload/meta/templates/meta-progress.md +25 -0
- package/dist/forge-payload/meta/templates/meta-retrospective.md +28 -0
- package/dist/forge-payload/meta/templates/meta-sprint-manifest.md +26 -0
- package/dist/forge-payload/meta/templates/meta-sprint-requirements.md +91 -0
- package/dist/forge-payload/meta/templates/meta-task-prompt.md +26 -0
- package/dist/forge-payload/meta/tool-specs/collate.spec.md +88 -0
- package/dist/forge-payload/meta/tool-specs/generation-manifest.spec.md +139 -0
- package/dist/forge-payload/meta/tool-specs/manage-config.spec.md +143 -0
- package/dist/forge-payload/meta/tool-specs/seed-store.spec.md +91 -0
- package/dist/forge-payload/meta/tool-specs/store-cli.spec.md +328 -0
- package/dist/forge-payload/meta/tool-specs/validate-store.spec.md +191 -0
- package/dist/forge-payload/meta/workflows/_fragments/context-injection.md +75 -0
- package/dist/forge-payload/meta/workflows/_fragments/event-emission-schema.md +73 -0
- package/dist/forge-payload/meta/workflows/_fragments/finalize.md +13 -0
- package/dist/forge-payload/meta/workflows/_fragments/friction-emit.md +73 -0
- package/dist/forge-payload/meta/workflows/_fragments/progress-reporting.md +38 -0
- package/dist/forge-payload/meta/workflows/_fragments/store-cli-verbs.md +39 -0
- package/dist/forge-payload/meta/workflows/meta-approve.md +119 -0
- package/dist/forge-payload/meta/workflows/meta-collate.md +89 -0
- package/dist/forge-payload/meta/workflows/meta-commit.md +93 -0
- package/dist/forge-payload/meta/workflows/meta-enhance.md +286 -0
- package/dist/forge-payload/meta/workflows/meta-fix-bug.md +501 -0
- package/dist/forge-payload/meta/workflows/meta-implement.md +132 -0
- package/dist/forge-payload/meta/workflows/meta-migrate.md +455 -0
- package/dist/forge-payload/meta/workflows/meta-orchestrate.md +993 -0
- package/dist/forge-payload/meta/workflows/meta-plan-task.md +133 -0
- package/dist/forge-payload/meta/workflows/meta-quiz-agent.md +135 -0
- package/dist/forge-payload/meta/workflows/meta-retrospective.md +65 -0
- package/dist/forge-payload/meta/workflows/meta-review-implementation.md +119 -0
- package/dist/forge-payload/meta/workflows/meta-review-plan.md +108 -0
- package/dist/forge-payload/meta/workflows/meta-review-sprint-completion.md +65 -0
- package/dist/forge-payload/meta/workflows/meta-sprint-intake.md +76 -0
- package/dist/forge-payload/meta/workflows/meta-sprint-plan.md +147 -0
- package/dist/forge-payload/meta/workflows/meta-update-implementation.md +76 -0
- package/dist/forge-payload/meta/workflows/meta-update-plan.md +76 -0
- package/dist/forge-payload/meta/workflows/meta-validate.md +111 -0
- package/dist/forge-payload/tools/check-structure.cjs +344 -0
- package/dist/forge-payload/tools/collate.cjs +34 -9
- package/dist/forge-payload/tools/list-skills.js +76 -0
- package/dist/forge-payload/tools/parse-gates.cjs +8 -2
- package/dist/forge-payload/tools/store-cli.cjs +56 -11
- package/dist/forge-payload/tools/store.cjs +61 -0
- package/dist/forge-payload/tools/substitute-placeholders.cjs +60 -8
- package/dist/forge-payload/tools/validate-store.cjs +6 -2
- package/dist/forge-payload/tools/verify-integrity.cjs +86 -0
- package/package.json +2 -2
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Forge tool: check-structure
|
|
5
|
+
// Checks that all files listed in structure-manifest.json are present
|
|
6
|
+
// in a project's generated output.
|
|
7
|
+
// Usage: node check-structure.cjs [--strict] [--path <project-root>] [--validate-manifest] [--forge-root <path>]
|
|
8
|
+
// --path <project-root> Directory to check against (default: process.cwd())
|
|
9
|
+
// --strict Also report files present but NOT in the manifest (extra files)
|
|
10
|
+
// --validate-manifest Validate manifest against base-pack source files
|
|
11
|
+
// --forge-root <path> Path to the forge/ plugin directory (required with --validate-manifest)
|
|
12
|
+
//
|
|
13
|
+
// Reads .forge/config.json paths.* for directory overrides.
|
|
14
|
+
// Falls back to manifest dir field if config is absent or unparseable.
|
|
15
|
+
//
|
|
16
|
+
// Exit 0: all expected files present (or only extras found without --strict)
|
|
17
|
+
// Exit 1: any missing files detected; also exit 1 if extras found with --strict
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { getCommandsSubdir } = require('./lib/paths.cjs');
|
|
22
|
+
|
|
23
|
+
// ── Per-namespace verification logic ──────────────────────────────────────────
|
|
24
|
+
//
|
|
25
|
+
// Returns { present, missing, extra, total } without calling process.exit.
|
|
26
|
+
// - present: number of expected files found
|
|
27
|
+
// - missing: array of { nsKey, dir, filename }
|
|
28
|
+
// - extra: array of { nsKey, dir, filename } (populated only when strict=true)
|
|
29
|
+
// - total: total number of expected files across all namespaces
|
|
30
|
+
|
|
31
|
+
function checkNamespaces(manifest, projectRoot, options = {}) {
|
|
32
|
+
const { strict = false, configPaths = null } = options;
|
|
33
|
+
|
|
34
|
+
// Resolve config path overrides
|
|
35
|
+
let resolvedConfigPaths;
|
|
36
|
+
let projectPrefix = '';
|
|
37
|
+
if (configPaths !== null) {
|
|
38
|
+
resolvedConfigPaths = configPaths;
|
|
39
|
+
} else {
|
|
40
|
+
resolvedConfigPaths = {};
|
|
41
|
+
const configFile = path.join(projectRoot, '.forge', 'config.json');
|
|
42
|
+
if (fs.existsSync(configFile)) {
|
|
43
|
+
try {
|
|
44
|
+
const cfg = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
45
|
+
resolvedConfigPaths = (cfg && cfg.paths) ? cfg.paths : {};
|
|
46
|
+
projectPrefix = (cfg && cfg.project && cfg.project.prefix)
|
|
47
|
+
? getCommandsSubdir(cfg.project.prefix)
|
|
48
|
+
: '';
|
|
49
|
+
} catch {
|
|
50
|
+
// unparseable — use defaults
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let totalPresent = 0;
|
|
56
|
+
let totalExpected = 0;
|
|
57
|
+
const allMissing = [];
|
|
58
|
+
const allExtra = [];
|
|
59
|
+
|
|
60
|
+
for (const [nsKey, ns] of Object.entries(manifest.namespaces)) {
|
|
61
|
+
const logicalKey = ns.logicalKey || nsKey;
|
|
62
|
+
let resolvedDir = (resolvedConfigPaths[logicalKey] && typeof resolvedConfigPaths[logicalKey] === 'string')
|
|
63
|
+
? resolvedConfigPaths[logicalKey]
|
|
64
|
+
: ns.dir;
|
|
65
|
+
if (ns.prefixed && projectPrefix) {
|
|
66
|
+
resolvedDir = resolvedDir + '/' + projectPrefix;
|
|
67
|
+
}
|
|
68
|
+
const absDir = path.join(projectRoot, resolvedDir);
|
|
69
|
+
|
|
70
|
+
const expected = ns.files || [];
|
|
71
|
+
totalExpected += expected.length;
|
|
72
|
+
|
|
73
|
+
const present = [];
|
|
74
|
+
const missing = [];
|
|
75
|
+
for (const filename of expected) {
|
|
76
|
+
const fullPath = path.join(absDir, filename);
|
|
77
|
+
if (fs.existsSync(fullPath)) {
|
|
78
|
+
present.push(filename);
|
|
79
|
+
} else {
|
|
80
|
+
missing.push({ nsKey, dir: resolvedDir, filename });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
totalPresent += present.length;
|
|
85
|
+
allMissing.push(...missing);
|
|
86
|
+
|
|
87
|
+
if (strict) {
|
|
88
|
+
if (fs.existsSync(absDir)) {
|
|
89
|
+
try {
|
|
90
|
+
const expectedSet = new Set(expected);
|
|
91
|
+
const found = fs.readdirSync(absDir);
|
|
92
|
+
for (const f of found) {
|
|
93
|
+
if (!expectedSet.has(f)) {
|
|
94
|
+
allExtra.push({ nsKey, dir: resolvedDir, filename: f });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
present: totalPresent,
|
|
104
|
+
missing: allMissing,
|
|
105
|
+
extra: allExtra,
|
|
106
|
+
total: totalExpected,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Manifest validation against base-pack ──────────────────────────────────────
|
|
111
|
+
//
|
|
112
|
+
// Convention-based reverse mapping: each manifest namespace's dir field maps
|
|
113
|
+
// to a base-pack source directory. For schemas, the source is forgeRoot/schemas/.
|
|
114
|
+
// This function validates that every file in the manifest has a corresponding
|
|
115
|
+
// base-pack source, and vice versa.
|
|
116
|
+
//
|
|
117
|
+
// Returns { manifestOnly, basePackOnly }
|
|
118
|
+
|
|
119
|
+
function validateManifest(manifest, forgeRoot) {
|
|
120
|
+
const basePackDir = path.join(forgeRoot, 'init', 'base-pack');
|
|
121
|
+
|
|
122
|
+
// Convention-based reverse mapping from manifest dir to base-pack source dir
|
|
123
|
+
const nsToSourceDir = {
|
|
124
|
+
personas: path.join(basePackDir, 'personas'),
|
|
125
|
+
skills: path.join(basePackDir, 'skills'),
|
|
126
|
+
workflows: path.join(basePackDir, 'workflows'),
|
|
127
|
+
fragments: path.join(basePackDir, 'workflows', '_fragments'),
|
|
128
|
+
templates: path.join(basePackDir, 'templates'),
|
|
129
|
+
commands: path.join(basePackDir, 'commands'),
|
|
130
|
+
// schemas: not base-pack-sourced — source is forgeRoot/schemas/
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const manifestOnly = []; // Files in manifest but not in base-pack
|
|
134
|
+
const basePackOnly = []; // Files in base-pack but not in manifest
|
|
135
|
+
|
|
136
|
+
for (const [nsKey, ns] of Object.entries(manifest.namespaces)) {
|
|
137
|
+
// Skip schemas — they ship from forgeRoot/schemas/, not base-pack
|
|
138
|
+
if (nsKey === 'schemas') continue;
|
|
139
|
+
|
|
140
|
+
const sourceDir = nsToSourceDir[nsKey];
|
|
141
|
+
if (!sourceDir) continue;
|
|
142
|
+
|
|
143
|
+
const manifestFiles = new Set(ns.files || []);
|
|
144
|
+
|
|
145
|
+
// Read base-pack source directory
|
|
146
|
+
let basePackFiles = [];
|
|
147
|
+
try {
|
|
148
|
+
basePackFiles = fs.readdirSync(sourceDir).filter(f => {
|
|
149
|
+
// Only include regular files, skip subdirectories (like _fragments under workflows)
|
|
150
|
+
const stat = fs.statSync(path.join(sourceDir, f));
|
|
151
|
+
return stat.isFile();
|
|
152
|
+
});
|
|
153
|
+
} catch {
|
|
154
|
+
// Directory doesn't exist — all manifest files are manifestOnly
|
|
155
|
+
for (const f of manifestFiles) {
|
|
156
|
+
manifestOnly.push({ nsKey, filename: f, dir: ns.dir });
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const basePackFileSet = new Set(basePackFiles);
|
|
162
|
+
|
|
163
|
+
// Files in manifest but not in base-pack
|
|
164
|
+
for (const f of manifestFiles) {
|
|
165
|
+
if (!basePackFileSet.has(f)) {
|
|
166
|
+
manifestOnly.push({ nsKey, filename: f, dir: ns.dir });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Files in base-pack but not in manifest
|
|
171
|
+
for (const f of basePackFileSet) {
|
|
172
|
+
if (!manifestFiles.has(f)) {
|
|
173
|
+
basePackOnly.push({ nsKey, filename: f, dir: ns.dir });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { manifestOnly, basePackOnly };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Exports ────────────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
module.exports = { checkNamespaces, validateManifest };
|
|
184
|
+
|
|
185
|
+
// ── CLI ────────────────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
if (require.main === module) {
|
|
188
|
+
try {
|
|
189
|
+
// ── Parse arguments ──────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
const argv = process.argv.slice(2);
|
|
192
|
+
let projectRoot = process.cwd();
|
|
193
|
+
let strict = false;
|
|
194
|
+
let validateManifestFlag = false;
|
|
195
|
+
let forgeRoot = null;
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < argv.length; i++) {
|
|
198
|
+
if (argv[i] === '--path' && argv[i + 1]) {
|
|
199
|
+
projectRoot = path.resolve(argv[++i]);
|
|
200
|
+
} else if (argv[i] === '--strict') {
|
|
201
|
+
strict = true;
|
|
202
|
+
} else if (argv[i] === '--validate-manifest') {
|
|
203
|
+
validateManifestFlag = true;
|
|
204
|
+
} else if (argv[i] === '--forge-root' && argv[i + 1]) {
|
|
205
|
+
forgeRoot = path.resolve(argv[++i]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── Load structure-manifest.json ─────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
const manifestPath = forgeRoot
|
|
212
|
+
? path.join(forgeRoot, 'schemas', 'structure-manifest.json')
|
|
213
|
+
: path.join(__dirname, '..', 'schemas', 'structure-manifest.json');
|
|
214
|
+
if (!fs.existsSync(manifestPath)) {
|
|
215
|
+
process.stderr.write(`× structure-manifest.json not found at ${manifestPath}\n`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let manifest;
|
|
220
|
+
try {
|
|
221
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
222
|
+
} catch (e) {
|
|
223
|
+
process.stderr.write(`× Failed to parse structure-manifest.json: ${e.message}\n`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── Validate manifest against base-pack (if requested) ──────────────────────
|
|
228
|
+
|
|
229
|
+
if (validateManifestFlag) {
|
|
230
|
+
if (!forgeRoot) {
|
|
231
|
+
process.stderr.write('× --validate-manifest requires --forge-root <path>\n');
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const { manifestOnly, basePackOnly } = validateManifest(manifest, forgeRoot);
|
|
236
|
+
|
|
237
|
+
if (manifestOnly.length > 0) {
|
|
238
|
+
process.stdout.write('× Files in manifest but absent from base-pack:\n');
|
|
239
|
+
for (const { nsKey, filename, dir } of manifestOnly) {
|
|
240
|
+
process.stdout.write(` × ${dir}/${filename} (namespace: ${nsKey})\n`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (basePackOnly.length > 0) {
|
|
245
|
+
process.stdout.write('△ Files in base-pack but absent from manifest:\n');
|
|
246
|
+
for (const { nsKey, filename, dir } of basePackOnly) {
|
|
247
|
+
process.stdout.write(` △ ${dir}/${filename} (namespace: ${nsKey})\n`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (manifestOnly.length === 0 && basePackOnly.length === 0) {
|
|
252
|
+
process.stdout.write('〇 Manifest and base-pack are in sync.\n');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (manifestOnly.length > 0) {
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ── Check namespaces ─────────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
const result = checkNamespaces(manifest, projectRoot, { strict });
|
|
264
|
+
|
|
265
|
+
// ── Format output ────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
// Group results by namespace for display
|
|
268
|
+
const byNs = {};
|
|
269
|
+
for (const m of result.missing) {
|
|
270
|
+
if (!byNs[m.nsKey]) byNs[m.nsKey] = { present: 0, missing: [], extra: [] };
|
|
271
|
+
byNs[m.nsKey].missing.push(m.filename);
|
|
272
|
+
}
|
|
273
|
+
for (const e of result.extra) {
|
|
274
|
+
if (!byNs[e.nsKey]) byNs[e.nsKey] = { present: 0, missing: [], extra: [] };
|
|
275
|
+
byNs[e.nsKey].extra.push(e.filename);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Count present per namespace
|
|
279
|
+
for (const [nsKey, ns] of Object.entries(manifest.namespaces)) {
|
|
280
|
+
if (!byNs[nsKey]) byNs[nsKey] = { present: 0, missing: [], extra: [] };
|
|
281
|
+
const total = (ns.files || []).length;
|
|
282
|
+
const missingCount = byNs[nsKey].missing.length;
|
|
283
|
+
byNs[nsKey].present = total - missingCount;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const lines = [];
|
|
287
|
+
let anyMissing = result.missing.length > 0;
|
|
288
|
+
let anyExtra = result.extra.length > 0;
|
|
289
|
+
|
|
290
|
+
for (const [nsKey, ns] of Object.entries(manifest.namespaces)) {
|
|
291
|
+
const logicalKey = ns.logicalKey || nsKey;
|
|
292
|
+
const info = byNs[nsKey] || { present: 0, missing: [], extra: [] };
|
|
293
|
+
const total = (ns.files || []).length;
|
|
294
|
+
const dir = ns.dir;
|
|
295
|
+
|
|
296
|
+
if (info.missing.length === 0 && info.extra.length === 0) {
|
|
297
|
+
lines.push(`〇 ${dir}/ — ${info.present}/${total} present`);
|
|
298
|
+
} else {
|
|
299
|
+
if (info.missing.length > 0) {
|
|
300
|
+
lines.push(`× ${dir}/ — ${info.present}/${total} present, ${info.missing.length} missing:`);
|
|
301
|
+
for (const f of info.missing) {
|
|
302
|
+
lines.push(` × ${f}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (info.extra.length > 0) {
|
|
306
|
+
if (info.missing.length === 0) {
|
|
307
|
+
lines.push(`△ ${dir}/ — ${info.present}/${total} present, ${info.extra.length} extra:`);
|
|
308
|
+
}
|
|
309
|
+
for (const f of info.extra) {
|
|
310
|
+
lines.push(` △ ${f} (not in manifest)`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for (const line of lines) {
|
|
317
|
+
process.stdout.write(line + '\n');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!anyMissing && !anyExtra) {
|
|
321
|
+
process.stdout.write(`〇 Structure check: all ${result.total} expected files present.\n`);
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const parts = [`${result.present} present`];
|
|
326
|
+
if (result.missing.length > 0) parts.push(`${result.missing.length} missing`);
|
|
327
|
+
if (strict && result.extra.length > 0) parts.push(`${result.extra.length} extra`);
|
|
328
|
+
process.stdout.write(`── Structure check: ${parts.join(', ')} (of ${result.total} expected)\n`);
|
|
329
|
+
|
|
330
|
+
if (anyMissing) {
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
// Extra-only without --strict → exit 0 already handled above
|
|
334
|
+
// Extra-only with --strict
|
|
335
|
+
if (strict && anyExtra) {
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
process.exit(0);
|
|
339
|
+
|
|
340
|
+
} catch (err) {
|
|
341
|
+
process.stderr.write(`× check-structure fatal: ${err.message}\n${err.stack}\n`);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
} // end if (require.main === module)
|
|
@@ -952,10 +952,16 @@ for (const bug of allBugs) {
|
|
|
952
952
|
// When purging events for a bug, aggregate cost data from event files
|
|
953
953
|
// before they are deleted. The aggregated cost summary is embedded in
|
|
954
954
|
// the bug's INDEX.md so the information survives the purge.
|
|
955
|
+
//
|
|
956
|
+
// Bug-phase events live in the shared `events/bugs/` virtual sprint dir
|
|
957
|
+
// (see meta-fix-bug.md § Event Emission). Read from that dir and filter
|
|
958
|
+
// by `event.bugId === bug.bugId` — the per-bug `events/{bugId}/` path
|
|
959
|
+
// never existed (silent data loss before this fix).
|
|
955
960
|
let costTotals;
|
|
956
961
|
if (PURGE_EVENTS && SPRINT_ARG && SPRINT_ARG === bug.bugId) {
|
|
957
|
-
const { events } = loadSprintEvents(
|
|
958
|
-
const
|
|
962
|
+
const { events } = loadSprintEvents('bugs');
|
|
963
|
+
const bugEvents = events.filter(e => e.bugId === bug.bugId);
|
|
964
|
+
const tokenEvents = bugEvents.filter(e => e.inputTokens !== undefined);
|
|
959
965
|
if (tokenEvents.length > 0) {
|
|
960
966
|
const totals = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, estimatedCostUSD: 0, sources: new Set() };
|
|
961
967
|
for (const e of tokenEvents) {
|
|
@@ -1022,16 +1028,35 @@ const tag = DRY_RUN ? '[dry-run] ' : '';
|
|
|
1022
1028
|
console.log(`${tag}Collated: ${targetSprints.length} sprint(s), ${allBugs.length} bug(s) → MASTER_INDEX.md updated, ${sprintIndexesWritten} sprint INDEX(es), ${taskIndexesWritten} task INDEX(es), ${bugIndexesWritten} bug INDEX(es), ${costReportsWritten} COST_REPORT(s) written`);
|
|
1023
1029
|
|
|
1024
1030
|
// --- Purge event directory if requested ---
|
|
1031
|
+
//
|
|
1032
|
+
// Bug arg → purge only the bug-matching events from the shared
|
|
1033
|
+
// `events/bugs/` dir (filter by event.bugId). Sprint arg → purge the
|
|
1034
|
+
// whole sprint event directory as before.
|
|
1025
1035
|
if (PURGE_EVENTS) {
|
|
1026
|
-
const relDir = path.relative(cwd, path.join(storeRoot, 'events', SPRINT_ARG));
|
|
1027
1036
|
try {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1037
|
+
let result;
|
|
1038
|
+
let relDir;
|
|
1039
|
+
if (IS_BUG_ARG) {
|
|
1040
|
+
relDir = path.relative(cwd, path.join(storeRoot, 'events', 'bugs'));
|
|
1041
|
+
result = _getStore().purgeBugEvents(SPRINT_ARG, { dryRun: DRY_RUN });
|
|
1042
|
+
const label = `'${SPRINT_ARG}' in ${relDir}/`;
|
|
1043
|
+
if (result.fileCount === 0) {
|
|
1044
|
+
console.log(`${tag}Purge: no events for ${label} — nothing to delete`);
|
|
1045
|
+
} else if (DRY_RUN) {
|
|
1046
|
+
console.log(`[dry-run] would purge: ${label} (${result.fileCount} file(s))`);
|
|
1047
|
+
} else {
|
|
1048
|
+
console.log(`Purged: ${label} (${result.fileCount} event file(s) deleted)`);
|
|
1049
|
+
}
|
|
1033
1050
|
} else {
|
|
1034
|
-
|
|
1051
|
+
relDir = path.relative(cwd, path.join(storeRoot, 'events', SPRINT_ARG));
|
|
1052
|
+
result = _getStore().purgeEvents(SPRINT_ARG, { dryRun: DRY_RUN });
|
|
1053
|
+
if (result.fileCount === 0) {
|
|
1054
|
+
console.log(`${tag}Purge: no events directory found for '${SPRINT_ARG}' — nothing to delete`);
|
|
1055
|
+
} else if (DRY_RUN) {
|
|
1056
|
+
console.log(`[dry-run] would purge: ${relDir}/ (${result.fileCount} file(s))`);
|
|
1057
|
+
} else {
|
|
1058
|
+
console.log(`Purged: ${relDir}/ (${result.fileCount} event file(s) deleted)`);
|
|
1059
|
+
}
|
|
1035
1060
|
}
|
|
1036
1061
|
} catch (err) {
|
|
1037
1062
|
console.error(`Error: ${err.message}`);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Forge skill query helper
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// node list-skills.js — print all available skill names (one per line)
|
|
6
|
+
// node list-skills.js <skill-name> — exit 0 if available, exit 1 if not
|
|
7
|
+
//
|
|
8
|
+
// Sources checked:
|
|
9
|
+
// ~/.claude/plugins/installed_plugins.json — marketplace plugins
|
|
10
|
+
// scope "user" → always available
|
|
11
|
+
// scope "local" → only if projectPath matches cwd
|
|
12
|
+
// ~/.claude/skills/ — personal skills (subdirs with SKILL.md)
|
|
13
|
+
//
|
|
14
|
+
// Uses only Node.js built-ins — no npm dependencies required.
|
|
15
|
+
// Works on Linux, macOS, and Windows wherever Claude Code runs.
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
// On unexpected failure, exit 0 so hook callers degrade gracefully
|
|
20
|
+
// (assume skill absent / no output) rather than propagating an error.
|
|
21
|
+
process.on('uncaughtException', () => process.exit(0));
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const os = require('os');
|
|
26
|
+
|
|
27
|
+
const pluginsFile = process.env.CLAUDE_PLUGIN_DATA_ROOT
|
|
28
|
+
? path.join(process.env.CLAUDE_PLUGIN_DATA_ROOT, 'installed_plugins.json')
|
|
29
|
+
: path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
30
|
+
|
|
31
|
+
const personalSkillsDir = process.env.CLAUDE_SKILLS_DIR
|
|
32
|
+
|| path.join(os.homedir(), '.claude', 'skills');
|
|
33
|
+
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const skills = new Set();
|
|
36
|
+
|
|
37
|
+
// Source 1: marketplace plugins from installed_plugins.json
|
|
38
|
+
if (fs.existsSync(pluginsFile)) {
|
|
39
|
+
try {
|
|
40
|
+
const data = JSON.parse(fs.readFileSync(pluginsFile, 'utf8'));
|
|
41
|
+
for (const [key, installs] of Object.entries(data.plugins || {})) {
|
|
42
|
+
for (const install of installs) {
|
|
43
|
+
const isUser = install.scope === 'user';
|
|
44
|
+
const isLocal = install.scope === 'local' && install.projectPath === cwd;
|
|
45
|
+
if (isUser || isLocal) {
|
|
46
|
+
skills.add(key.split('@')[0]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch { /* non-fatal */ }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Source 2: personal skills — subdirectories containing a SKILL.md
|
|
54
|
+
if (fs.existsSync(personalSkillsDir)) {
|
|
55
|
+
try {
|
|
56
|
+
for (const entry of fs.readdirSync(personalSkillsDir, { withFileTypes: true })) {
|
|
57
|
+
if (!entry.isDirectory()) continue;
|
|
58
|
+
const skillMd = path.join(personalSkillsDir, entry.name, 'SKILL.md');
|
|
59
|
+
if (fs.existsSync(skillMd)) {
|
|
60
|
+
skills.add(entry.name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch { /* non-fatal */ }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const sorted = [...skills].sort();
|
|
67
|
+
const query = process.argv[2];
|
|
68
|
+
|
|
69
|
+
if (!query) {
|
|
70
|
+
// Print all available skill names
|
|
71
|
+
if (sorted.length > 0) process.stdout.write(sorted.join('\n') + '\n');
|
|
72
|
+
process.exit(0);
|
|
73
|
+
} else {
|
|
74
|
+
// Exit 0 if skill is available, 1 if not
|
|
75
|
+
process.exit(sorted.includes(query) ? 0 : 1);
|
|
76
|
+
}
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
const FENCE_OPEN = /^```gates\s+phase=([A-Za-z0-9_-]+)\s*$/;
|
|
19
19
|
const FENCE_CLOSE = /^```\s*$/;
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// Verdicts a workflow can carry in an `after <phase> = <verdict>` directive.
|
|
22
|
+
// Mirrors `read-verdict.cjs § ALLOWED_VERDICTS` — both must accept the same
|
|
23
|
+
// set. `n/a` is the legitimate verdict for setup phases (plan, implement,
|
|
24
|
+
// triage) that do not produce an approval/revision signal. Rejecting it
|
|
25
|
+
// here caused EMBERGLOW-BUG-001 (v0.44.1) to halt at preflight exit 2
|
|
26
|
+
// when the new fix-bug meta tried to use `after triage = n/a`.
|
|
27
|
+
const VALID_VERDICTS = new Set(['approved', 'revision', 'n/a']);
|
|
22
28
|
|
|
23
29
|
function parseGates(markdown) {
|
|
24
30
|
if (typeof markdown !== 'string' || markdown.length === 0) return {};
|
|
@@ -142,7 +148,7 @@ function parseAfter(rest, lineNo) {
|
|
|
142
148
|
const verdict = m[2].toLowerCase();
|
|
143
149
|
if (!VALID_VERDICTS.has(verdict)) {
|
|
144
150
|
throw new Error(
|
|
145
|
-
`parse-gates: line ${lineNo}: "after" verdict must be "approved" or "
|
|
151
|
+
`parse-gates: line ${lineNo}: "after" verdict must be "approved", "revision", or "n/a", got "${m[2]}"`,
|
|
146
152
|
);
|
|
147
153
|
}
|
|
148
154
|
return { phase: m[1], verdict };
|
|
@@ -140,6 +140,12 @@ const { suggest, suggestEntityType, formatSuggestion } = require('./lib/suggest.
|
|
|
140
140
|
const VALID_SUMMARY_PHASES = new Set(['plan', 'review_plan', 'implementation', 'code_review', 'validation', 'triage', 'approve']);
|
|
141
141
|
|
|
142
142
|
// Schema for a single phase summary (used by set-summary / set-bug-summary)
|
|
143
|
+
// Mirror of bug.schema.json § $defs.phaseSummary. Both definitions MUST stay
|
|
144
|
+
// in sync — this constant validates set-summary / set-bug-summary writes;
|
|
145
|
+
// the JSON schema validates entity reads. `route` is an optional triage-only
|
|
146
|
+
// field carrying the fix-bug pipeline route decision (A | B) and exists here
|
|
147
|
+
// for bug.summaries.triage in particular; non-triage phases simply do not
|
|
148
|
+
// set it.
|
|
143
149
|
const PHASE_SUMMARY_SCHEMA = {
|
|
144
150
|
type: 'object',
|
|
145
151
|
required: ['objective', 'written_at'],
|
|
@@ -149,7 +155,8 @@ const PHASE_SUMMARY_SCHEMA = {
|
|
|
149
155
|
findings: { type: 'array', items: { type: 'string', maxLength: 200 }, maxItems: 12 },
|
|
150
156
|
verdict: { type: 'string', enum: ['approved', 'revision', 'n/a'] },
|
|
151
157
|
written_at: { type: 'string' },
|
|
152
|
-
artifact_ref:{ type: 'string' }
|
|
158
|
+
artifact_ref:{ type: 'string' },
|
|
159
|
+
route: { type: 'string', enum: ['A', 'B'] }
|
|
153
160
|
},
|
|
154
161
|
additionalProperties: false
|
|
155
162
|
};
|
|
@@ -181,12 +188,21 @@ const SPRINT_TRANSITIONS = {
|
|
|
181
188
|
};
|
|
182
189
|
|
|
183
190
|
const BUG_TRANSITIONS = {
|
|
184
|
-
reported:
|
|
185
|
-
triaged:
|
|
186
|
-
'in-progress': ['fixed'
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
//
|
|
191
|
+
reported: ['triaged'],
|
|
192
|
+
triaged: ['in-progress'],
|
|
193
|
+
'in-progress': ['fixed'],
|
|
194
|
+
// Terminal: fixed
|
|
195
|
+
//
|
|
196
|
+
// The `approved` and `verified` enum members were removed (forge#GH-NNN).
|
|
197
|
+
// The architect-approve verdict signal for bugs travels through
|
|
198
|
+
// `bug.summaries.approve.verdict` (see read-verdict.cjs §
|
|
199
|
+
// BUG_PHASE_VERDICT_SOURCE), not `bug.status`. Keeping them in the enum
|
|
200
|
+
// created an LLM-translation trap whereby a task-shaped approve workflow
|
|
201
|
+
// run on a bug improvised `update-status bug ... approved`, which failed
|
|
202
|
+
// either at the schema layer (illegal transition out of terminal
|
|
203
|
+
// `verified`) or, post-FORGE-BUG-002 preflight defence, at the gate
|
|
204
|
+
// layer (forbid bug.status == approved). Dropping the enum removes the
|
|
205
|
+
// trap at its source.
|
|
190
206
|
};
|
|
191
207
|
|
|
192
208
|
const FEATURE_TRANSITIONS = {
|
|
@@ -205,7 +221,7 @@ const TRANSITION_MAP = {
|
|
|
205
221
|
const TERMINAL_STATES = new Set([
|
|
206
222
|
'committed', 'abandoned', // task
|
|
207
223
|
'retrospective-done', // sprint
|
|
208
|
-
'
|
|
224
|
+
'fixed', // bug
|
|
209
225
|
'shipped', 'retired' // feature
|
|
210
226
|
]);
|
|
211
227
|
|
|
@@ -373,6 +389,32 @@ function listEntities(entity, filter) {
|
|
|
373
389
|
case 'task': return store.listTasks(filter);
|
|
374
390
|
case 'bug': return store.listBugs(filter);
|
|
375
391
|
case 'feature': return store.listFeatures(filter);
|
|
392
|
+
case 'event': {
|
|
393
|
+
// Defect D fix: traverse all sub-directories under events/ — sprints write
|
|
394
|
+
// to events/<sprintId>/, bugs to events/bugs/, enhancements to events/enhancement/.
|
|
395
|
+
// Return the union of all event JSONs across every sub-directory, skipping
|
|
396
|
+
// sidecar files (_-prefixed) and non-JSON files.
|
|
397
|
+
const eventsBase = path.join(store.impl.storeRoot, 'events');
|
|
398
|
+
if (!fs.existsSync(eventsBase)) return [];
|
|
399
|
+
const allEvents = [];
|
|
400
|
+
const subDirs = fs.readdirSync(eventsBase, { withFileTypes: true });
|
|
401
|
+
for (const entry of subDirs) {
|
|
402
|
+
if (!entry.isDirectory()) continue;
|
|
403
|
+
const subDir = path.join(eventsBase, entry.name);
|
|
404
|
+
const files = fs.readdirSync(subDir).filter(
|
|
405
|
+
(f) => f.endsWith('.json') && !f.startsWith('_')
|
|
406
|
+
);
|
|
407
|
+
for (const file of files) {
|
|
408
|
+
try {
|
|
409
|
+
const rec = JSON.parse(fs.readFileSync(path.join(subDir, file), 'utf8'));
|
|
410
|
+
if (rec && (!filter || Object.entries(filter).every(([k, v]) => rec[k] === v))) {
|
|
411
|
+
allEvents.push(rec);
|
|
412
|
+
}
|
|
413
|
+
} catch (_) { /* skip malformed files */ }
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return allEvents;
|
|
417
|
+
}
|
|
376
418
|
default:
|
|
377
419
|
console.error(`Unknown entity type: ${entity}${formatSuggestion(suggestEntityType(entity, ['sprint', 'task', 'bug', 'feature']))}`);
|
|
378
420
|
process.exit(1);
|
|
@@ -619,7 +661,7 @@ function cmdList() {
|
|
|
619
661
|
}
|
|
620
662
|
|
|
621
663
|
if (!ENTITY_TYPES.includes(entity)) {
|
|
622
|
-
console.error(`Unknown entity type: ${entity}${formatSuggestion(suggestEntityType(entity, ['sprint', 'task', 'bug', 'feature']))}`);
|
|
664
|
+
console.error(`Unknown entity type: ${entity}${formatSuggestion(suggestEntityType(entity, ['sprint', 'task', 'bug', 'event', 'feature']))}`);
|
|
623
665
|
process.exit(1);
|
|
624
666
|
}
|
|
625
667
|
|
|
@@ -785,10 +827,13 @@ function cmdEmit() {
|
|
|
785
827
|
}
|
|
786
828
|
|
|
787
829
|
// FK check: reject sprintIds that are not in the store and not reserved.
|
|
788
|
-
//
|
|
830
|
+
// Reserved sprintIds:
|
|
831
|
+
// - SYS-* — system-generated events that predate sprint records
|
|
832
|
+
// - bugs — virtual sprint dir for fix-bug phase events (see
|
|
833
|
+
// meta/tool-specs/validate-store.spec.md §"event.sprintId")
|
|
789
834
|
// --allow-synthetic bypasses the check for test-harness or synthetic events.
|
|
790
835
|
if (!allowSynthetic) {
|
|
791
|
-
const RESERVED_GLOB = /^SYS
|
|
836
|
+
const RESERVED_GLOB = /^(SYS-|bugs$)/;
|
|
792
837
|
if (!RESERVED_GLOB.test(sprintId)) {
|
|
793
838
|
const validSprintIds = resolveValidSprintIds();
|
|
794
839
|
if (!validSprintIds.includes(sprintId)) {
|