@entelligentsia/forgecli 0.8.4 → 0.9.0
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 +53 -0
- package/dist/bin/argv.d.ts +2 -2
- package/dist/bin/argv.js +17 -0
- package/dist/bin/argv.js.map +1 -1
- package/dist/bin/config.d.ts +69 -0
- package/dist/bin/config.js +315 -0
- package/dist/bin/config.js.map +1 -0
- package/dist/bin/doctor.d.ts +1 -0
- package/dist/bin/doctor.js +12 -0
- package/dist/bin/doctor.js.map +1 -1
- package/dist/bin/forge.js +7 -0
- package/dist/bin/forge.js.map +1 -1
- package/dist/extensions/forgecli/config-command.d.ts +8 -0
- package/dist/extensions/forgecli/config-command.js +66 -0
- package/dist/extensions/forgecli/config-command.js.map +1 -0
- package/dist/extensions/forgecli/config-layer.d.ts +38 -0
- package/dist/extensions/forgecli/config-layer.js +68 -0
- package/dist/extensions/forgecli/config-layer.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/component.d.ts +35 -0
- package/dist/extensions/forgecli/config-tui/component.js +236 -0
- package/dist/extensions/forgecli/config-tui/component.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/handler.d.ts +40 -0
- package/dist/extensions/forgecli/config-tui/handler.js +240 -0
- package/dist/extensions/forgecli/config-tui/handler.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/index.d.ts +5 -0
- package/dist/extensions/forgecli/config-tui/index.js +5 -0
- package/dist/extensions/forgecli/config-tui/index.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/keys.d.ts +26 -0
- package/dist/extensions/forgecli/config-tui/keys.js +33 -0
- package/dist/extensions/forgecli/config-tui/keys.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/plugin-config-reader.d.ts +23 -0
- package/dist/extensions/forgecli/config-tui/plugin-config-reader.js +58 -0
- package/dist/extensions/forgecli/config-tui/plugin-config-reader.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/advanced-menu.d.ts +7 -0
- package/dist/extensions/forgecli/config-tui/screens/advanced-menu.js +83 -0
- package/dist/extensions/forgecli/config-tui/screens/advanced-menu.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/confirm-quit.d.ts +11 -0
- package/dist/extensions/forgecli/config-tui/screens/confirm-quit.js +54 -0
- package/dist/extensions/forgecli/config-tui/screens/confirm-quit.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/override-editor.d.ts +11 -0
- package/dist/extensions/forgecli/config-tui/screens/override-editor.js +233 -0
- package/dist/extensions/forgecli/config-tui/screens/override-editor.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.d.ts +7 -0
- package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.js +91 -0
- package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/overrides-list.d.ts +7 -0
- package/dist/extensions/forgecli/config-tui/screens/overrides-list.js +71 -0
- package/dist/extensions/forgecli/config-tui/screens/overrides-list.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/persona-editor.d.ts +10 -0
- package/dist/extensions/forgecli/config-tui/screens/persona-editor.js +182 -0
- package/dist/extensions/forgecli/config-tui/screens/persona-editor.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/persona-picker.d.ts +7 -0
- package/dist/extensions/forgecli/config-tui/screens/persona-picker.js +76 -0
- package/dist/extensions/forgecli/config-tui/screens/persona-picker.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/personas-list.d.ts +7 -0
- package/dist/extensions/forgecli/config-tui/screens/personas-list.js +98 -0
- package/dist/extensions/forgecli/config-tui/screens/personas-list.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/shared.d.ts +29 -0
- package/dist/extensions/forgecli/config-tui/screens/shared.js +100 -0
- package/dist/extensions/forgecli/config-tui/screens/shared.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/show-resolved.d.ts +23 -0
- package/dist/extensions/forgecli/config-tui/screens/show-resolved.js +128 -0
- package/dist/extensions/forgecli/config-tui/screens/show-resolved.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/tier-menu.d.ts +7 -0
- package/dist/extensions/forgecli/config-tui/screens/tier-menu.js +135 -0
- package/dist/extensions/forgecli/config-tui/screens/tier-menu.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/tier-picker.d.ts +9 -0
- package/dist/extensions/forgecli/config-tui/screens/tier-picker.js +122 -0
- package/dist/extensions/forgecli/config-tui/screens/tier-picker.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens/types.d.ts +24 -0
- package/dist/extensions/forgecli/config-tui/screens/types.js +5 -0
- package/dist/extensions/forgecli/config-tui/screens/types.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/screens.d.ts +24 -0
- package/dist/extensions/forgecli/config-tui/screens.js +78 -0
- package/dist/extensions/forgecli/config-tui/screens.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state/buffer.d.ts +11 -0
- package/dist/extensions/forgecli/config-tui/state/buffer.js +91 -0
- package/dist/extensions/forgecli/config-tui/state/buffer.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state/constants.d.ts +4 -0
- package/dist/extensions/forgecli/config-tui/state/constants.js +14 -0
- package/dist/extensions/forgecli/config-tui/state/constants.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state/index.d.ts +6 -0
- package/dist/extensions/forgecli/config-tui/state/index.js +9 -0
- package/dist/extensions/forgecli/config-tui/state/index.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state/init.d.ts +2 -0
- package/dist/extensions/forgecli/config-tui/state/init.js +30 -0
- package/dist/extensions/forgecli/config-tui/state/init.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state/model.d.ts +192 -0
- package/dist/extensions/forgecli/config-tui/state/model.js +4 -0
- package/dist/extensions/forgecli/config-tui/state/model.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state/reducer.d.ts +2 -0
- package/dist/extensions/forgecli/config-tui/state/reducer.js +212 -0
- package/dist/extensions/forgecli/config-tui/state/reducer.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state/selectors.d.ts +91 -0
- package/dist/extensions/forgecli/config-tui/state/selectors.js +231 -0
- package/dist/extensions/forgecli/config-tui/state/selectors.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/state.d.ts +6 -0
- package/dist/extensions/forgecli/config-tui/state.js +11 -0
- package/dist/extensions/forgecli/config-tui/state.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/theme.d.ts +37 -0
- package/dist/extensions/forgecli/config-tui/theme.js +88 -0
- package/dist/extensions/forgecli/config-tui/theme.js.map +1 -0
- package/dist/extensions/forgecli/config-tui/tier-meta.d.ts +28 -0
- package/dist/extensions/forgecli/config-tui/tier-meta.js +69 -0
- package/dist/extensions/forgecli/config-tui/tier-meta.js.map +1 -0
- package/dist/extensions/forgecli/config-writer.d.ts +16 -0
- package/dist/extensions/forgecli/config-writer.js +63 -0
- package/dist/extensions/forgecli/config-writer.js.map +1 -0
- package/dist/extensions/forgecli/fix-bug.js +85 -1
- package/dist/extensions/forgecli/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/forge-cli-schema.json +54 -0
- package/dist/extensions/forgecli/forge-commands.js +3 -8
- package/dist/extensions/forgecli/forge-commands.js.map +1 -1
- package/dist/extensions/forgecli/forge-subagent.d.ts +13 -0
- package/dist/extensions/forgecli/forge-subagent.js +19 -0
- package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
- package/dist/extensions/forgecli/index.js +16 -0
- package/dist/extensions/forgecli/index.js.map +1 -1
- package/dist/extensions/forgecli/input-router.d.ts +33 -0
- package/dist/extensions/forgecli/input-router.js +133 -0
- package/dist/extensions/forgecli/input-router.js.map +1 -0
- package/dist/extensions/forgecli/model-resolver.d.ts +32 -0
- package/dist/extensions/forgecli/model-resolver.js +65 -0
- package/dist/extensions/forgecli/model-resolver.js.map +1 -0
- package/dist/extensions/forgecli/model-validator.d.ts +29 -0
- package/dist/extensions/forgecli/model-validator.js +107 -0
- package/dist/extensions/forgecli/model-validator.js.map +1 -0
- package/dist/extensions/forgecli/run-sprint.js +59 -0
- package/dist/extensions/forgecli/run-sprint.js.map +1 -1
- package/dist/extensions/forgecli/run-task.js +93 -1
- package/dist/extensions/forgecli/run-task.js.map +1 -1
- package/dist/extensions/forgecli/thread-switcher.js +5 -2
- package/dist/extensions/forgecli/thread-switcher.js.map +1 -1
- package/dist/extensions/forgecli/whats-new-widget.js +5 -2
- package/dist/extensions/forgecli/whats-new-widget.js.map +1 -1
- package/package.json +11 -3
- package/dist/extensions/forgecli/review-command.d.ts +0 -2
- package/dist/extensions/forgecli/review-command.js +0 -184
- package/dist/extensions/forgecli/review-command.js.map +0 -1
- package/dist/forge-payload/.tools/banners.cjs +0 -435
- package/dist/forge-payload/.tools/build-context-pack.cjs +0 -290
- package/dist/forge-payload/.tools/build-init-context.cjs +0 -322
- package/dist/forge-payload/.tools/build-overlay.cjs +0 -326
- package/dist/forge-payload/.tools/build-persona-pack.cjs +0 -226
- package/dist/forge-payload/.tools/collate.cjs +0 -1041
- package/dist/forge-payload/.tools/generation-manifest.cjs +0 -311
- package/dist/forge-payload/.tools/lib/forge-root.cjs +0 -59
- package/dist/forge-payload/.tools/lib/paths.cjs +0 -29
- package/dist/forge-payload/.tools/lib/pricing.cjs +0 -165
- package/dist/forge-payload/.tools/lib/project-root.cjs +0 -32
- package/dist/forge-payload/.tools/lib/result.js +0 -40
- package/dist/forge-payload/.tools/lib/store-facade.cjs +0 -162
- package/dist/forge-payload/.tools/lib/store-nlp.cjs +0 -250
- package/dist/forge-payload/.tools/lib/store-query-exec.cjs +0 -272
- package/dist/forge-payload/.tools/lib/validate.js +0 -141
- package/dist/forge-payload/.tools/manage-config.cjs +0 -340
- package/dist/forge-payload/.tools/manage-versions.cjs +0 -365
- package/dist/forge-payload/.tools/package.json +0 -3
- package/dist/forge-payload/.tools/parse-gates.cjs +0 -151
- package/dist/forge-payload/.tools/parse-verdict.cjs +0 -67
- package/dist/forge-payload/.tools/preflight-gate.cjs +0 -350
- package/dist/forge-payload/.tools/prompts/sprint-plan-prompt.md +0 -70
- package/dist/forge-payload/.tools/schemas/task-list.schema.json +0 -53
- package/dist/forge-payload/.tools/seed-store.cjs +0 -237
- package/dist/forge-payload/.tools/store-cli.cjs +0 -1226
- package/dist/forge-payload/.tools/store-query.cjs +0 -319
- package/dist/forge-payload/.tools/store.cjs +0 -315
- package/dist/forge-payload/.tools/substitute-placeholders.cjs +0 -625
- package/dist/forge-payload/.tools/validate-store.cjs +0 -593
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
// Forge tool: manage-versions
|
|
5
|
-
// Manage .forge/structure-versions.json — snapshot lifecycle for structural elements.
|
|
6
|
-
//
|
|
7
|
-
// Subcommands:
|
|
8
|
-
// init Write snapshot 0 at first init (idempotent: no-op if file exists)
|
|
9
|
-
// current Print current snapshot index and metadata
|
|
10
|
-
// list Print tabular summary of all snapshots
|
|
11
|
-
// add-snapshot Archive current structural elements and record a new snapshot entry
|
|
12
|
-
//
|
|
13
|
-
// Composition model: Working Artifact = base@pluginVersion + snapshot@currentSnapshot + user_enhancements
|
|
14
|
-
// Snapshot-array invariant: snapshots is ordered ascending by index; currentSnapshot always equals
|
|
15
|
-
// snapshots[snapshots.length - 1].index.
|
|
16
|
-
//
|
|
17
|
-
// Usage:
|
|
18
|
-
// node manage-versions.cjs init [--dry-run]
|
|
19
|
-
// node manage-versions.cjs current
|
|
20
|
-
// node manage-versions.cjs list
|
|
21
|
-
// node manage-versions.cjs add-snapshot --source <source> [--enhanced-elements <csv>] [--dry-run]
|
|
22
|
-
// --source <string> Required. One of: post-init | post-sprint:<ID> | on-demand
|
|
23
|
-
// --enhanced-elements <csv> Optional. Comma-separated list of .forge/-relative paths that were enhanced.
|
|
24
|
-
// --dry-run Log intent without performing I/O.
|
|
25
|
-
//
|
|
26
|
-
// Environment:
|
|
27
|
-
// FORGE_ROOT — path to forge plugin root (used by init to locate plugin.json and schemas)
|
|
28
|
-
// Falls back to auto-detection via ../../ from __dirname when FORGE_ROOT is unset.
|
|
29
|
-
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const path = require('path');
|
|
32
|
-
const { resolveForgeRoot } = require('./lib/forge-root.cjs');
|
|
33
|
-
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Constants
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
// Relative path suffix for structure-versions.json (from project root)
|
|
39
|
-
const VERSIONS_SUFFIX = path.join('.forge', 'structure-versions.json');
|
|
40
|
-
|
|
41
|
-
// Default project root is cwd when used as CLI; exports allow test injection.
|
|
42
|
-
const VERSIONS_PATH = path.join(process.cwd(), VERSIONS_SUFFIX);
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// Exported helpers (used by unit tests and callers)
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the path to structure-versions.json for the given project root.
|
|
50
|
-
* @param {string} projectRoot
|
|
51
|
-
* @returns {string}
|
|
52
|
-
*/
|
|
53
|
-
function versionsPath(projectRoot) {
|
|
54
|
-
return path.join(projectRoot, VERSIONS_SUFFIX);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Read and parse .forge/structure-versions.json.
|
|
59
|
-
* @param {string} projectRoot
|
|
60
|
-
* @returns {object} parsed document
|
|
61
|
-
* @throws {Error} when file does not exist or cannot be parsed
|
|
62
|
-
*/
|
|
63
|
-
function readStructureVersions(projectRoot) {
|
|
64
|
-
const filePath = versionsPath(projectRoot);
|
|
65
|
-
if (!fs.existsSync(filePath)) {
|
|
66
|
-
throw new Error(`structure-versions.json not found at ${filePath}. Run \`manage-versions init\` first.`);
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
70
|
-
} catch (e) {
|
|
71
|
-
throw new Error(`Failed to parse structure-versions.json at ${filePath}: ${e.message}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Write data to .forge/structure-versions.json atomically via .tmp.PID rename.
|
|
77
|
-
* @param {string} projectRoot
|
|
78
|
-
* @param {object} data
|
|
79
|
-
*/
|
|
80
|
-
function writeStructureVersions(projectRoot, data) {
|
|
81
|
-
const filePath = versionsPath(projectRoot);
|
|
82
|
-
const json = JSON.stringify(data, null, 2) + '\n';
|
|
83
|
-
const tmp = filePath + '.tmp.' + process.pid;
|
|
84
|
-
try {
|
|
85
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
86
|
-
fs.writeFileSync(tmp, json, 'utf8');
|
|
87
|
-
fs.renameSync(tmp, filePath);
|
|
88
|
-
} catch (e) {
|
|
89
|
-
try { fs.unlinkSync(tmp); } catch {}
|
|
90
|
-
throw new Error(`Failed to write structure-versions.json: ${e.message}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Resolve forge root from env or __dirname fallback.
|
|
96
|
-
* Delegates to the shared forge-root.cjs helper (FR-001).
|
|
97
|
-
* @param {string} [envForgeRoot] - value of FORGE_ROOT env var
|
|
98
|
-
* @returns {string}
|
|
99
|
-
*/
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Read the plugin version from FORGE_ROOT/.claude-plugin/plugin.json.
|
|
103
|
-
* @param {string} forgeRoot
|
|
104
|
-
* @returns {string}
|
|
105
|
-
*/
|
|
106
|
-
function readPluginVersion(forgeRoot) {
|
|
107
|
-
const pluginPath = path.join(forgeRoot, '.claude-plugin', 'plugin.json');
|
|
108
|
-
try {
|
|
109
|
-
const pkg = JSON.parse(fs.readFileSync(pluginPath, 'utf8'));
|
|
110
|
-
if (!pkg.version) throw new Error('version field missing');
|
|
111
|
-
return pkg.version;
|
|
112
|
-
} catch (e) {
|
|
113
|
-
throw new Error(`Failed to read plugin version from ${pluginPath}: ${e.message}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Read the overlay tool version from FORGE_ROOT/schemas/project-overlay.schema.json.
|
|
119
|
-
* Falls back to "1.0.0" if the schema does not contain a version field.
|
|
120
|
-
* @param {string} forgeRoot
|
|
121
|
-
* @returns {string}
|
|
122
|
-
*/
|
|
123
|
-
function readOverlayToolVersion(forgeRoot) {
|
|
124
|
-
const schemaPath = path.join(forgeRoot, 'schemas', 'project-overlay.schema.json');
|
|
125
|
-
try {
|
|
126
|
-
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
|
127
|
-
if (typeof schema.version === 'string' && schema.version) {
|
|
128
|
-
return schema.version;
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
// Schema unreadable — fall back
|
|
132
|
-
}
|
|
133
|
-
return '1.0.0';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Structural element directories that are eligible to be archived.
|
|
137
|
-
const STRUCTURAL_ELEMENT_DIRS = ['personas', 'skills', 'workflows', 'templates'];
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Copy a file, creating intermediate directories as needed.
|
|
141
|
-
* @param {string} src
|
|
142
|
-
* @param {string} dest
|
|
143
|
-
*/
|
|
144
|
-
function copyFileWithDirs(src, dest) {
|
|
145
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
146
|
-
fs.copyFileSync(src, dest);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Add a new snapshot entry to structure-versions.json and archive the
|
|
151
|
-
* current structural elements listed in enhancedElements.
|
|
152
|
-
*
|
|
153
|
-
* @param {string} projectRoot - path to the project root (where .forge/ lives)
|
|
154
|
-
* @param {string} source - snapshot source label (post-init | post-sprint:<ID> | on-demand)
|
|
155
|
-
* @param {string[]} enhancedElements - list of .forge/-relative paths that were enhanced
|
|
156
|
-
* @param {boolean} [dryRun] - when true, log intent but perform no I/O
|
|
157
|
-
*/
|
|
158
|
-
function addSnapshot(projectRoot, source, enhancedElements, dryRun) {
|
|
159
|
-
if (!source) {
|
|
160
|
-
throw new Error('--source is required for add-snapshot. Provide one of: post-init | post-sprint:<ID> | on-demand');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const doc = readStructureVersions(projectRoot);
|
|
164
|
-
const nextIndex = doc.currentSnapshot + 1;
|
|
165
|
-
const archivePath = path.join('.forge', 'archive', `snap-${nextIndex}`);
|
|
166
|
-
const archiveAbsPath = path.join(projectRoot, archivePath);
|
|
167
|
-
|
|
168
|
-
// Guard: fail if archive directory already exists to prevent corruption.
|
|
169
|
-
if (fs.existsSync(archiveAbsPath)) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
`Archive directory already exists: ${archiveAbsPath} (snap-${nextIndex}). ` +
|
|
172
|
-
'Cannot create snapshot — remove or rename the existing archive directory first.'
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const createdAt = new Date().toISOString();
|
|
177
|
-
const newSnapshot = {
|
|
178
|
-
index: nextIndex,
|
|
179
|
-
createdAt,
|
|
180
|
-
source,
|
|
181
|
-
enhancedElements: enhancedElements || [],
|
|
182
|
-
archivePath
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
if (dryRun) {
|
|
186
|
-
console.log(`[dry-run] Would create archive at: ${archiveAbsPath}`);
|
|
187
|
-
console.log(`[dry-run] Would archive ${(enhancedElements || []).length} element(s).`);
|
|
188
|
-
console.log(`[dry-run] Would write snapshot entry:`);
|
|
189
|
-
console.log(JSON.stringify(newSnapshot, null, 2));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Archive each enhanced element by copying from .forge/ into the archive dir.
|
|
194
|
-
for (const relPath of (enhancedElements || [])) {
|
|
195
|
-
const srcPath = path.join(projectRoot, '.forge', relPath);
|
|
196
|
-
const destPath = path.join(archiveAbsPath, relPath);
|
|
197
|
-
if (fs.existsSync(srcPath)) {
|
|
198
|
-
copyFileWithDirs(srcPath, destPath);
|
|
199
|
-
}
|
|
200
|
-
// If the source file does not exist, skip silently — the element list
|
|
201
|
-
// may reference files that were removed or renamed; archiving what's there
|
|
202
|
-
// is better than failing the whole snapshot.
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Also create the archive directory even if no elements were listed,
|
|
206
|
-
// so archivePath references a real directory.
|
|
207
|
-
fs.mkdirSync(archiveAbsPath, { recursive: true });
|
|
208
|
-
|
|
209
|
-
// Append snapshot entry and advance currentSnapshot.
|
|
210
|
-
doc.snapshots.push(newSnapshot);
|
|
211
|
-
doc.currentSnapshot = nextIndex;
|
|
212
|
-
writeStructureVersions(projectRoot, doc);
|
|
213
|
-
|
|
214
|
-
console.log(`ノ add-snapshot complete — snapshot ${nextIndex} written (source: ${source}, elements: ${(enhancedElements || []).length})`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Initialise structure-versions.json with snapshot 0.
|
|
219
|
-
* Idempotent: if the file already exists, exits cleanly without overwriting.
|
|
220
|
-
*
|
|
221
|
-
* @param {string} projectRoot - path to the project root (where .forge/ lives)
|
|
222
|
-
* @param {string} forgeRoot - path to the forge plugin root
|
|
223
|
-
* @param {boolean} [dryRun] - when true, log intent but perform no I/O
|
|
224
|
-
* @param {string} [source] - source label for snapshot 0 (default: 'base-pack')
|
|
225
|
-
*/
|
|
226
|
-
function initStructureVersions(projectRoot, forgeRoot, dryRun, source) {
|
|
227
|
-
const filePath = versionsPath(projectRoot);
|
|
228
|
-
|
|
229
|
-
// Idempotency: if the file already exists, do nothing.
|
|
230
|
-
if (fs.existsSync(filePath)) {
|
|
231
|
-
console.log(`〇 structure-versions.json already exists — skipping (idempotent).`);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const effectiveSource = source || 'base-pack';
|
|
236
|
-
const basePackVersion = readPluginVersion(forgeRoot);
|
|
237
|
-
const overlayToolVersion = readOverlayToolVersion(forgeRoot);
|
|
238
|
-
|
|
239
|
-
const doc = {
|
|
240
|
-
basePackVersion,
|
|
241
|
-
overlayToolVersion,
|
|
242
|
-
currentSnapshot: 0,
|
|
243
|
-
snapshots: [
|
|
244
|
-
{
|
|
245
|
-
index: 0,
|
|
246
|
-
createdAt: new Date().toISOString(),
|
|
247
|
-
source: effectiveSource,
|
|
248
|
-
enhancedElements: [],
|
|
249
|
-
archivePath: null
|
|
250
|
-
}
|
|
251
|
-
]
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
if (dryRun) {
|
|
255
|
-
console.log('[dry-run] Would write structure-versions.json:');
|
|
256
|
-
console.log(JSON.stringify(doc, null, 2));
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
writeStructureVersions(projectRoot, doc);
|
|
261
|
-
console.log(`ノ structure-versions.json written (snapshot 0, source: ${effectiveSource}, plugin: v${basePackVersion})`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// ---------------------------------------------------------------------------
|
|
265
|
-
// Exports (for unit tests)
|
|
266
|
-
// ---------------------------------------------------------------------------
|
|
267
|
-
|
|
268
|
-
module.exports = {
|
|
269
|
-
initStructureVersions,
|
|
270
|
-
addSnapshot,
|
|
271
|
-
readStructureVersions,
|
|
272
|
-
writeStructureVersions,
|
|
273
|
-
VERSIONS_PATH,
|
|
274
|
-
versionsPath,
|
|
275
|
-
readPluginVersion,
|
|
276
|
-
readOverlayToolVersion,
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
// ---------------------------------------------------------------------------
|
|
280
|
-
// CLI
|
|
281
|
-
// ---------------------------------------------------------------------------
|
|
282
|
-
|
|
283
|
-
if (require.main === module) {
|
|
284
|
-
|
|
285
|
-
process.on('uncaughtException', (error) => {
|
|
286
|
-
console.error('Fatal manage-versions error:', error.message);
|
|
287
|
-
process.exit(1);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const args = process.argv.slice(2);
|
|
291
|
-
const DRY_RUN = args.includes('--dry-run');
|
|
292
|
-
const subcommand = args.find(a => !a.startsWith('--'));
|
|
293
|
-
|
|
294
|
-
const forgeRoot = resolveForgeRoot(process.env.FORGE_ROOT);
|
|
295
|
-
const projectRoot = process.cwd();
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
switch (subcommand) {
|
|
299
|
-
case 'init': {
|
|
300
|
-
const sourceIdx = args.indexOf('--source');
|
|
301
|
-
const initSource = sourceIdx !== -1 ? args[sourceIdx + 1] : undefined;
|
|
302
|
-
initStructureVersions(projectRoot, forgeRoot, DRY_RUN, initSource);
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
case 'current': {
|
|
307
|
-
const doc = readStructureVersions(projectRoot);
|
|
308
|
-
const snap = doc.snapshots.find(s => s.index === doc.currentSnapshot);
|
|
309
|
-
console.log(`Current snapshot: ${doc.currentSnapshot}`);
|
|
310
|
-
if (snap) {
|
|
311
|
-
console.log(` source: ${snap.source}`);
|
|
312
|
-
console.log(` createdAt: ${snap.createdAt}`);
|
|
313
|
-
console.log(` plugin: v${doc.basePackVersion}`);
|
|
314
|
-
}
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
case 'list': {
|
|
319
|
-
const doc = readStructureVersions(projectRoot);
|
|
320
|
-
console.log(`Snapshots (current: ${doc.currentSnapshot}):`);
|
|
321
|
-
console.log(`${'#'.padEnd(4)} ${'source'.padEnd(20)} ${'createdAt'.padEnd(28)} elements`);
|
|
322
|
-
console.log('-'.repeat(70));
|
|
323
|
-
for (const snap of doc.snapshots) {
|
|
324
|
-
const current = snap.index === doc.currentSnapshot ? '*' : ' ';
|
|
325
|
-
const elems = snap.enhancedElements ? snap.enhancedElements.length : 0;
|
|
326
|
-
console.log(`${current}${String(snap.index).padEnd(3)} ${snap.source.padEnd(20)} ${snap.createdAt.padEnd(28)} ${elems}`);
|
|
327
|
-
}
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
case 'add-snapshot': {
|
|
332
|
-
// Parse --source flag
|
|
333
|
-
const sourceIdx = args.indexOf('--source');
|
|
334
|
-
const source = sourceIdx !== -1 ? args[sourceIdx + 1] : null;
|
|
335
|
-
if (!source || source.startsWith('--')) {
|
|
336
|
-
console.error('× add-snapshot requires --source <value>.');
|
|
337
|
-
console.error(' Accepted values: post-init | post-sprint:<SPRINT_ID> | on-demand');
|
|
338
|
-
process.exit(1);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Parse optional --enhanced-elements flag (comma-separated list)
|
|
342
|
-
const elementsIdx = args.indexOf('--enhanced-elements');
|
|
343
|
-
let enhancedElements = [];
|
|
344
|
-
if (elementsIdx !== -1) {
|
|
345
|
-
const raw = args[elementsIdx + 1];
|
|
346
|
-
if (raw && !raw.startsWith('--')) {
|
|
347
|
-
enhancedElements = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
addSnapshot(projectRoot, source, enhancedElements, DRY_RUN);
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
default: {
|
|
356
|
-
console.error(`× Unknown subcommand: ${subcommand || '(none)'}`);
|
|
357
|
-
console.error(' Usage: manage-versions.cjs <init|current|list|add-snapshot> [--dry-run]');
|
|
358
|
-
process.exit(1);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
} catch (err) {
|
|
362
|
-
console.error(`× manage-versions error: ${err.message}`);
|
|
363
|
-
process.exit(1);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// parse-gates.cjs — minimal DSL for per-phase gate declarations.
|
|
4
|
-
//
|
|
5
|
-
// Grammar (closed — unknown directives throw):
|
|
6
|
-
// artifact <path> [min=<bytes>]
|
|
7
|
-
// require <field> <op> <value>
|
|
8
|
-
// forbid <field> <op> <value>
|
|
9
|
-
// after <phase> = <approved|revision>
|
|
10
|
-
//
|
|
11
|
-
// Predicate ops: ==, !=, in [v1, v2, ...]
|
|
12
|
-
// Blank lines and lines beginning with # (optionally indented) are ignored.
|
|
13
|
-
// Gates live in fenced blocks self-identified by phase via a fence-label
|
|
14
|
-
// attribute, e.g. ```gates phase=implement . The fence is self-contained;
|
|
15
|
-
// gate blocks can appear anywhere in the workflow file without depending on
|
|
16
|
-
// surrounding heading structure.
|
|
17
|
-
|
|
18
|
-
const FENCE_OPEN = /^```gates\s+phase=([A-Za-z0-9_-]+)\s*$/;
|
|
19
|
-
const FENCE_CLOSE = /^```\s*$/;
|
|
20
|
-
|
|
21
|
-
const VALID_VERDICTS = new Set(['approved', 'revision']);
|
|
22
|
-
|
|
23
|
-
function parseGates(markdown) {
|
|
24
|
-
if (typeof markdown !== 'string' || markdown.length === 0) return {};
|
|
25
|
-
const lines = markdown.split('\n');
|
|
26
|
-
const result = {};
|
|
27
|
-
|
|
28
|
-
let currentPhase = null;
|
|
29
|
-
let inFence = false;
|
|
30
|
-
let fenceStartLine = -1;
|
|
31
|
-
let fenceBuffer = [];
|
|
32
|
-
|
|
33
|
-
for (let i = 0; i < lines.length; i++) {
|
|
34
|
-
const line = lines[i];
|
|
35
|
-
const lineNo = i + 1;
|
|
36
|
-
|
|
37
|
-
if (!inFence) {
|
|
38
|
-
const m = line.match(FENCE_OPEN);
|
|
39
|
-
if (m) {
|
|
40
|
-
currentPhase = m[1];
|
|
41
|
-
if (result[currentPhase]) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`parse-gates: line ${lineNo}: duplicate gates block for phase "${currentPhase}"`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
inFence = true;
|
|
47
|
-
fenceStartLine = lineNo;
|
|
48
|
-
fenceBuffer = [];
|
|
49
|
-
}
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Inside a gates fence
|
|
54
|
-
if (FENCE_CLOSE.test(line)) {
|
|
55
|
-
result[currentPhase] = parseBlock(fenceBuffer, fenceStartLine);
|
|
56
|
-
inFence = false;
|
|
57
|
-
currentPhase = null;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
fenceBuffer.push({ text: line, lineNo });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (inFence) {
|
|
64
|
-
throw new Error(`parse-gates: unterminated \`\`\`gates fence opened at line ${fenceStartLine}`);
|
|
65
|
-
}
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function parseBlock(bufferedLines, _fenceStartLine) {
|
|
70
|
-
const spec = { artifacts: [], require: [], forbid: [], after: [] };
|
|
71
|
-
for (const { text, lineNo } of bufferedLines) {
|
|
72
|
-
const trimmed = text.trim();
|
|
73
|
-
if (trimmed === '' || trimmed.startsWith('#')) continue;
|
|
74
|
-
|
|
75
|
-
const firstSpace = trimmed.search(/\s/);
|
|
76
|
-
if (firstSpace < 0) {
|
|
77
|
-
throw new Error(`parse-gates: line ${lineNo}: malformed directive "${trimmed}"`);
|
|
78
|
-
}
|
|
79
|
-
const directive = trimmed.slice(0, firstSpace);
|
|
80
|
-
const rest = trimmed.slice(firstSpace + 1).trim();
|
|
81
|
-
|
|
82
|
-
switch (directive) {
|
|
83
|
-
case 'artifact':
|
|
84
|
-
spec.artifacts.push(parseArtifact(rest, lineNo));
|
|
85
|
-
break;
|
|
86
|
-
case 'require':
|
|
87
|
-
spec.require.push(parsePredicate(rest, lineNo));
|
|
88
|
-
break;
|
|
89
|
-
case 'forbid':
|
|
90
|
-
spec.forbid.push(parsePredicate(rest, lineNo));
|
|
91
|
-
break;
|
|
92
|
-
case 'after':
|
|
93
|
-
spec.after.push(parseAfter(rest, lineNo));
|
|
94
|
-
break;
|
|
95
|
-
default:
|
|
96
|
-
throw new Error(`parse-gates: line ${lineNo}: unknown directive "${directive}"`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return spec;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function parseArtifact(rest, lineNo) {
|
|
103
|
-
const minMatch = rest.match(/\bmin=(\d+)\s*$/);
|
|
104
|
-
let path = rest;
|
|
105
|
-
let minBytes = 0;
|
|
106
|
-
if (minMatch) {
|
|
107
|
-
minBytes = parseInt(minMatch[1], 10);
|
|
108
|
-
path = rest.slice(0, minMatch.index).trim();
|
|
109
|
-
}
|
|
110
|
-
if (!path) {
|
|
111
|
-
throw new Error(`parse-gates: line ${lineNo}: artifact directive missing path`);
|
|
112
|
-
}
|
|
113
|
-
return { path, minBytes };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function parsePredicate(rest, lineNo) {
|
|
117
|
-
// in-list form: <field> in [v1, v2, ...]
|
|
118
|
-
const inMatch = rest.match(/^(\S+)\s+in\s+\[(.*)\]\s*$/);
|
|
119
|
-
if (inMatch) {
|
|
120
|
-
const field = inMatch[1];
|
|
121
|
-
const value = inMatch[2]
|
|
122
|
-
.split(',')
|
|
123
|
-
.map((v) => v.trim())
|
|
124
|
-
.filter((v) => v.length > 0);
|
|
125
|
-
return { field, op: 'in', value };
|
|
126
|
-
}
|
|
127
|
-
// binary form: <field> <op> <value>
|
|
128
|
-
const binMatch = rest.match(/^(\S+)\s+(==|!=)\s+(.+?)\s*$/);
|
|
129
|
-
if (binMatch) {
|
|
130
|
-
return { field: binMatch[1], op: binMatch[2], value: binMatch[3].trim() };
|
|
131
|
-
}
|
|
132
|
-
throw new Error(
|
|
133
|
-
`parse-gates: line ${lineNo}: unknown predicate op or malformed predicate "${rest}"`,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function parseAfter(rest, lineNo) {
|
|
138
|
-
const m = rest.match(/^(\S+)\s*=\s*(\S+)\s*$/);
|
|
139
|
-
if (!m) {
|
|
140
|
-
throw new Error(`parse-gates: line ${lineNo}: malformed "after" directive "${rest}"`);
|
|
141
|
-
}
|
|
142
|
-
const verdict = m[2].toLowerCase();
|
|
143
|
-
if (!VALID_VERDICTS.has(verdict)) {
|
|
144
|
-
throw new Error(
|
|
145
|
-
`parse-gates: line ${lineNo}: "after" verdict must be "approved" or "revision", got "${m[2]}"`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
return { phase: m[1], verdict };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
module.exports = { parseGates };
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Closed verdict vocabulary. Anything outside this map yields null so the
|
|
4
|
-
// orchestrator halts loudly instead of guessing from reviewer prose.
|
|
5
|
-
const APPROVED = new Set(['approved', 'approve']);
|
|
6
|
-
const REVISION = new Set([
|
|
7
|
-
'revision required',
|
|
8
|
-
'revision',
|
|
9
|
-
'needs revision',
|
|
10
|
-
'changes requested',
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
const VERDICT_LINE = /^[ \t]*\*\*verdict:\*\*[ \t]*(.+?)[ \t]*$/gim;
|
|
14
|
-
|
|
15
|
-
function parseVerdict(markdown) {
|
|
16
|
-
if (typeof markdown !== 'string' || markdown.length === 0) return null;
|
|
17
|
-
|
|
18
|
-
let lastValue = null;
|
|
19
|
-
let match;
|
|
20
|
-
VERDICT_LINE.lastIndex = 0;
|
|
21
|
-
while ((match = VERDICT_LINE.exec(markdown)) !== null) {
|
|
22
|
-
lastValue = match[1];
|
|
23
|
-
}
|
|
24
|
-
if (lastValue === null) return null;
|
|
25
|
-
|
|
26
|
-
const normalised = lastValue
|
|
27
|
-
.trim()
|
|
28
|
-
.replace(/^\[(.*)\]$/, '$1')
|
|
29
|
-
.trim()
|
|
30
|
-
.toLowerCase();
|
|
31
|
-
|
|
32
|
-
if (APPROVED.has(normalised)) return 'approved';
|
|
33
|
-
if (REVISION.has(normalised)) return 'revision';
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
module.exports = { parseVerdict };
|
|
38
|
-
|
|
39
|
-
// CLI shim: `node parse-verdict.cjs <path-to-review.md>`
|
|
40
|
-
// stdout: "approved" | "revision" | "unknown"
|
|
41
|
-
// exit codes: 0 approved, 1 revision, 2 unknown/malformed
|
|
42
|
-
if (require.main === module) {
|
|
43
|
-
const fs = require('node:fs');
|
|
44
|
-
const path = process.argv[2];
|
|
45
|
-
if (!path) {
|
|
46
|
-
process.stderr.write('Usage: parse-verdict.cjs <path-to-review.md>\n');
|
|
47
|
-
process.exit(2);
|
|
48
|
-
}
|
|
49
|
-
let contents;
|
|
50
|
-
try {
|
|
51
|
-
contents = fs.readFileSync(path, 'utf8');
|
|
52
|
-
} catch (err) {
|
|
53
|
-
process.stderr.write(`parse-verdict: cannot read ${path}: ${err.message}\n`);
|
|
54
|
-
process.exit(2);
|
|
55
|
-
}
|
|
56
|
-
const verdict = parseVerdict(contents);
|
|
57
|
-
if (verdict === 'approved') {
|
|
58
|
-
process.stdout.write('approved\n');
|
|
59
|
-
process.exit(0);
|
|
60
|
-
}
|
|
61
|
-
if (verdict === 'revision') {
|
|
62
|
-
process.stdout.write('revision\n');
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
process.stdout.write('unknown\n');
|
|
66
|
-
process.exit(2);
|
|
67
|
-
}
|