@entelligentsia/forgecli 0.20.0 → 0.20.3

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 (24) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/extensions/forgecli/forge-init/phase4-register.js +19 -0
  3. package/dist/extensions/forgecli/forge-init/phase4-register.js.map +1 -1
  4. package/dist/forge-payload/commands/update.md +23 -1
  5. package/dist/forge-payload/schemas/_defs/phaseSummary.schema.json +18 -0
  6. package/dist/forge-payload/schemas/bug.schema.json +40 -0
  7. package/dist/forge-payload/schemas/collation-state.schema.json +16 -0
  8. package/dist/forge-payload/schemas/config.schema.json +215 -0
  9. package/dist/forge-payload/schemas/enum-catalog.json +71 -0
  10. package/dist/forge-payload/schemas/event-sidecar.schema.json +22 -0
  11. package/dist/forge-payload/schemas/event.schema.json +184 -0
  12. package/dist/forge-payload/schemas/feature.schema.json +22 -0
  13. package/dist/forge-payload/schemas/progress-entry.schema.json +16 -0
  14. package/dist/forge-payload/schemas/project-context.schema.json +167 -0
  15. package/dist/forge-payload/schemas/project-overlay.schema.json +25 -0
  16. package/dist/forge-payload/schemas/proposal.schema.json +40 -0
  17. package/dist/forge-payload/schemas/sprint.schema.json +27 -0
  18. package/dist/forge-payload/schemas/structure-versions.schema.json +57 -0
  19. package/dist/forge-payload/schemas/task.schema.json +43 -0
  20. package/dist/forge-payload/schemas/transitions/bug.json +31 -0
  21. package/dist/forge-payload/schemas/transitions/sprint.json +46 -0
  22. package/dist/forge-payload/schemas/transitions/task.json +109 -0
  23. package/dist/forge-payload/tools/manage-config.cjs +120 -2
  24. package/package.json +3 -3
@@ -0,0 +1,46 @@
1
+ [
2
+ {
3
+ "from": "planning",
4
+ "to": [
5
+ "active",
6
+ "blocked",
7
+ "abandoned"
8
+ ]
9
+ },
10
+ {
11
+ "from": "active",
12
+ "to": [
13
+ "completed",
14
+ "partially-completed",
15
+ "blocked",
16
+ "abandoned"
17
+ ]
18
+ },
19
+ {
20
+ "from": "completed",
21
+ "to": [
22
+ "retrospective-done"
23
+ ]
24
+ },
25
+ {
26
+ "from": "partially-completed",
27
+ "to": [
28
+ "retrospective-done"
29
+ ]
30
+ },
31
+ {
32
+ "from": "retrospective-done",
33
+ "to": []
34
+ },
35
+ {
36
+ "from": "blocked",
37
+ "to": [
38
+ "active",
39
+ "abandoned"
40
+ ]
41
+ },
42
+ {
43
+ "from": "abandoned",
44
+ "to": []
45
+ }
46
+ ]
@@ -0,0 +1,109 @@
1
+ [
2
+ {
3
+ "from": "draft",
4
+ "to": [
5
+ "planned",
6
+ "blocked",
7
+ "escalated",
8
+ "abandoned"
9
+ ]
10
+ },
11
+ {
12
+ "from": "planned",
13
+ "to": [
14
+ "plan-approved",
15
+ "plan-revision-required",
16
+ "blocked",
17
+ "escalated",
18
+ "abandoned"
19
+ ]
20
+ },
21
+ {
22
+ "from": "plan-approved",
23
+ "to": [
24
+ "implementing",
25
+ "plan-revision-required",
26
+ "blocked",
27
+ "escalated",
28
+ "abandoned"
29
+ ]
30
+ },
31
+ {
32
+ "from": "implementing",
33
+ "to": [
34
+ "implemented",
35
+ "plan-revision-required",
36
+ "code-revision-required",
37
+ "blocked",
38
+ "escalated",
39
+ "abandoned"
40
+ ]
41
+ },
42
+ {
43
+ "from": "implemented",
44
+ "to": [
45
+ "review-approved",
46
+ "plan-revision-required",
47
+ "code-revision-required",
48
+ "blocked",
49
+ "escalated",
50
+ "abandoned"
51
+ ]
52
+ },
53
+ {
54
+ "from": "review-approved",
55
+ "to": [
56
+ "approved",
57
+ "plan-revision-required",
58
+ "code-revision-required",
59
+ "blocked",
60
+ "escalated",
61
+ "abandoned"
62
+ ]
63
+ },
64
+ {
65
+ "from": "approved",
66
+ "to": [
67
+ "committed",
68
+ "plan-revision-required",
69
+ "code-revision-required",
70
+ "blocked",
71
+ "escalated",
72
+ "abandoned"
73
+ ]
74
+ },
75
+ {
76
+ "from": "plan-revision-required",
77
+ "to": [
78
+ "planned",
79
+ "blocked",
80
+ "escalated",
81
+ "abandoned"
82
+ ]
83
+ },
84
+ {
85
+ "from": "code-revision-required",
86
+ "to": [
87
+ "implementing",
88
+ "blocked",
89
+ "escalated",
90
+ "abandoned"
91
+ ]
92
+ },
93
+ {
94
+ "from": "committed",
95
+ "to": []
96
+ },
97
+ {
98
+ "from": "blocked",
99
+ "to": []
100
+ },
101
+ {
102
+ "from": "escalated",
103
+ "to": []
104
+ },
105
+ {
106
+ "from": "abandoned",
107
+ "to": []
108
+ }
109
+ ]
@@ -8,6 +8,7 @@
8
8
  // manage-config pipeline add <name> --description <text> --phases <json>
9
9
  // manage-config pipeline get <name>
10
10
  // manage-config pipeline remove <name>
11
+ // manage-config backfill [--forge-root <path>] [--dry-run]
11
12
  // manage-config resolve-forge-root
12
13
  // manage-config set <key.path> <json-value>
13
14
 
@@ -106,8 +107,13 @@ function validatePhases(phases) {
106
107
  function parseArgs(argv) {
107
108
  const result = {};
108
109
  for (let i = 0; i < argv.length; i++) {
109
- if (argv[i].startsWith('--') && i + 1 < argv.length) {
110
- result[argv[i].slice(2)] = argv[++i];
110
+ if (argv[i].startsWith('--')) {
111
+ const key = argv[i].slice(2);
112
+ if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
113
+ result[key] = argv[++i];
114
+ } else {
115
+ result[key] = 'true'; // boolean flag
116
+ }
111
117
  }
112
118
  }
113
119
  return result;
@@ -129,6 +135,7 @@ if (!subcmd) {
129
135
  ' pipeline get <name> Print a pipeline in full',
130
136
  ' pipeline remove <name>',
131
137
  ' pipeline backfill-models Backfill model fields from role defaults',
138
+ ' backfill [--forge-root <path>] [--dry-run] Backfill missing config fields from schema defaults',
132
139
  ' resolve-forge-root Resolve Forge plugin root path',
133
140
  ' set <key.path> <json-value> Set an arbitrary value',
134
141
  ].join('\n'));
@@ -279,6 +286,117 @@ if (subcmd === 'set') {
279
286
  // FR-010: resolve-forge-root — resolve the Forge plugin root path using
280
287
  // three-tier priority: (1) CLAUDE_PLUGIN_ROOT env var, (2) cache/marketplace
281
288
  // scan by forgeRef, (3) paths.forgeRoot fallback.
289
+ if (subcmd === 'backfill') {
290
+ // backfill-schema: write defaults for missing config fields per the config schema.
291
+ // Reads $FORGE_ROOT/schemas/config.schema.json (or .schemas/ fallback).
292
+ // Writes schema-defined defaults for any missing required or optional path fields.
293
+ // Also writes the top-level `version` field from the bundled plugin version.
294
+ // Usage: manage-config backfill [--forge-root <path>] [--dry-run]
295
+ const flags = parseArgs(args);
296
+ const forgeRoot = flags['forge-root'] || process.env.FORGE_ROOT || path.resolve(__dirname, '..');
297
+ const dryRun = !!flags['dry-run'];
298
+
299
+ // Locate config schema
300
+ let schemaPath = path.join(forgeRoot, 'schemas', 'config.schema.json');
301
+ if (!fs.existsSync(schemaPath)) {
302
+ schemaPath = path.join(forgeRoot, '.schemas', 'config.schema.json');
303
+ }
304
+ if (!fs.existsSync(schemaPath)) {
305
+ console.error('× config.schema.json not found in schemas/ or .schemas/ under forge-root:', forgeRoot);
306
+ process.exit(1);
307
+ }
308
+
309
+ let schema;
310
+ try { schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); } catch (e) {
311
+ console.error(`× reading config schema: ${e.message}`); process.exit(1);
312
+ }
313
+
314
+ if (!fs.existsSync(CONFIG_PATH)) {
315
+ console.error('× .forge/config.json not found. Run /forge:init first.');
316
+ process.exit(1);
317
+ }
318
+ const { config, raw } = readConfig();
319
+
320
+ const backfilled = [];
321
+ const still = [];
322
+
323
+ // 1. Top-level `version` — write bundled plugin version if absent
324
+ if (!config.version) {
325
+ const pluginJsonPath = path.join(forgeRoot, '.claude-plugin', 'plugin.json');
326
+ if (fs.existsSync(pluginJsonPath)) {
327
+ try {
328
+ const plugin = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
329
+ if (plugin.version) {
330
+ config.version = plugin.version;
331
+ backfilled.push('version = ' + plugin.version);
332
+ }
333
+ } catch { /* skip */ }
334
+ }
335
+ if (!config.version) {
336
+ still.push('version (no plugin.json found)');
337
+ }
338
+ }
339
+
340
+ // 2. Walk schema properties, apply defaults for any missing nested fields
341
+ // Covers: paths.*, commands.*
342
+ const walkDefaults = (obj, schemaProps, prefix) => {
343
+ if (!schemaProps || typeof schemaProps !== 'object') return;
344
+ for (const [key, spec] of Object.entries(schemaProps)) {
345
+ const dotPath = prefix ? prefix + '.' + key : key;
346
+ if (spec.type === 'object' && spec.properties) {
347
+ // Nested object — ensure container exists, then recurse
348
+ if (obj[key] == null || typeof obj[key] !== 'object') obj[key] = {};
349
+ walkDefaults(obj[key], spec.properties, dotPath);
350
+ } else if (obj[key] === undefined && spec.default !== undefined) {
351
+ obj[key] = spec.default;
352
+ backfilled.push(dotPath + ' = ' + JSON.stringify(spec.default));
353
+ } else if (obj[key] === undefined && spec.default === undefined) {
354
+ // Track that it's still missing (informational only)
355
+ const isRequired = Array.isArray(schema.required) && schema.required.includes(key);
356
+ if (isRequired || (prefix === 'paths' && spec.description)) {
357
+ still.push(dotPath + ' (no default in schema)');
358
+ }
359
+ }
360
+ }
361
+ };
362
+
363
+ // Walk top-level properties
364
+ for (const [key, spec] of Object.entries(schema.properties || {})) {
365
+ if (key === 'version') continue; // handled above
366
+ if (spec.type === 'object' && spec.properties) {
367
+ if (config[key] == null || typeof config[key] !== 'object') config[key] = {};
368
+ walkDefaults(config[key], spec.properties, key);
369
+ } else if (config[key] === undefined && spec.default !== undefined) {
370
+ config[key] = spec.default;
371
+ backfilled.push(key + ' = ' + JSON.stringify(spec.default));
372
+ } else if (config[key] === undefined && spec.default === undefined) {
373
+ if (Array.isArray(schema.required) && schema.required.includes(key)) {
374
+ still.push(key + ' (required, no default)');
375
+ }
376
+ }
377
+ }
378
+
379
+ if (backfilled.length > 0) {
380
+ if (dryRun) {
381
+ console.log('Would backfill:');
382
+ for (const b of backfilled) console.log(' + ' + b);
383
+ } else {
384
+ writeConfig(config, detectIndent(raw));
385
+ console.log('〇 Backfilled ' + backfilled.length + ' field(s):');
386
+ for (const b of backfilled) console.log(' + ' + b);
387
+ }
388
+ } else {
389
+ console.log('〇 No missing fields to backfill.');
390
+ }
391
+
392
+ if (still.length > 0) {
393
+ console.log('△ Still missing (no schema default):');
394
+ for (const s of still) console.log(' - ' + s);
395
+ }
396
+
397
+ process.exit(0);
398
+ }
399
+
282
400
  if (subcmd === 'resolve-forge-root') {
283
401
  // Priority 1: CLAUDE_PLUGIN_ROOT env var (if set and directory exists)
284
402
  const envRoot = process.env.CLAUDE_PLUGIN_ROOT;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@entelligentsia/forgecli",
3
- "version": "0.20.0",
4
- "description": "Forge SDLC ported onto @earendil-works/pi-coding-agent production launcher with three bin aliases (forge/forgecli/4ge). Bundles a curated fork of pi-coding-agent vendored under earendil-works names.",
3
+ "version": "0.20.3",
4
+ "description": "Forge SDLC ported onto @earendil-works/pi-coding-agent \u2014 production launcher with three bin aliases (forge/forgecli/4ge). Bundles a curated fork of pi-coding-agent vendored under earendil-works names.",
5
5
  "license": "MIT",
6
6
  "author": "Entelligentsia",
7
7
  "homepage": "https://github.com/Entelligentsia/forge-cli#readme",
@@ -105,4 +105,4 @@
105
105
  "@earendil-works/pi-tui",
106
106
  "js-yaml"
107
107
  ]
108
- }
108
+ }