godpowers 3.13.0 → 3.13.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/lib/pillars.js CHANGED
@@ -114,6 +114,13 @@ const GODPOWERS_ARTIFACTS = [
114
114
  '.godpowers/design/PRODUCT.md'
115
115
  ];
116
116
 
117
+ // ===========================================================================
118
+ // Pillar model: parse pillar files, detect installed pillars, compute the
119
+ // per-task load set, and construct/initialize pillar files. Shared with the
120
+ // artifact-sync workflow below (init/ensurePillar/pillarStub/detect are also
121
+ // part of the public API).
122
+ // ===========================================================================
123
+
117
124
  function stripQuotes(value) {
118
125
  return String(value).trim().replace(/^['"]|['"]$/g, '');
119
126
  }
@@ -401,6 +408,12 @@ function writeFenced(filePath, begin, end, content) {
401
408
  fs.writeFileSync(filePath, next);
402
409
  }
403
410
 
411
+ // ===========================================================================
412
+ // Artifact-sync workflow: turn Godpowers artifacts (PRD/ARCH/...) into durable
413
+ // pillar signals and write them into the routed pillar files. Builds on the
414
+ // model above (init/ensurePillar/pillarStub/detect/buildProtocolContent).
415
+ // ===========================================================================
416
+
404
417
  function artifactToPillars(artifactPath) {
405
418
  const normalized = artifactPath.replace(/\\/g, '/');
406
419
  const pillars = [];
@@ -11,6 +11,7 @@ const path = require('path');
11
11
  const crypto = require('crypto');
12
12
 
13
13
  const state = require('./state');
14
+ const { exists } = require('./sync-fs');
14
15
 
15
16
  const MAX_FILE_BYTES = 80 * 1024;
16
17
  const MAX_SYSTEM_FILES = 80;
@@ -95,18 +96,10 @@ function rel(projectRoot, absPath) {
95
96
  return path.relative(projectRoot, absPath).split(path.sep).join('/');
96
97
  }
97
98
 
98
- function exists(projectRoot, relPath) {
99
- return fs.existsSync(path.join(projectRoot, relPath));
100
- }
101
-
102
99
  function ensureDir(filePath) {
103
100
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
104
101
  }
105
102
 
106
- function sha(input) {
107
- return crypto.createHash('sha256').update(input).digest('hex');
108
- }
109
-
110
103
  function hashFiles(projectRoot, files) {
111
104
  const h = crypto.createHash('sha256');
112
105
  for (const file of files.map((f) => f.path).sort()) {
@@ -476,7 +469,6 @@ module.exports = {
476
469
  _private: {
477
470
  classifyFile,
478
471
  extractSignals,
479
- filesForKinds,
480
- sha
472
+ filesForKinds
481
473
  }
482
474
  };
@@ -9,6 +9,10 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
 
11
11
  const recipes = require('./recipes');
12
+ const { read, write } = require('./sync-fs');
13
+ const { makeAddCheck } = require('./sync-check');
14
+
15
+ const addCheck = makeAddCheck('recipe-coverage');
12
16
 
13
17
  const LOG_PATH = '.godpowers/surface/RECIPE-COVERAGE-SYNC.md';
14
18
 
@@ -40,29 +44,6 @@ const REQUIRED_COVERAGE = [
40
44
  }
41
45
  ];
42
46
 
43
- function read(projectRoot, relPath) {
44
- const file = path.join(projectRoot, relPath);
45
- if (!fs.existsSync(file)) return '';
46
- return fs.readFileSync(file, 'utf8');
47
- }
48
-
49
- function write(projectRoot, relPath, content) {
50
- const file = path.join(projectRoot, relPath);
51
- fs.mkdirSync(path.dirname(file), { recursive: true });
52
- fs.writeFileSync(file, content);
53
- }
54
-
55
- function addCheck(checks, id, status, relPath, message, opts = {}) {
56
- checks.push({
57
- area: 'recipe-coverage',
58
- id,
59
- status,
60
- path: relPath,
61
- message,
62
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
63
- spawn: opts.spawn || null
64
- });
65
- }
66
47
 
67
48
  function recipePath(projectRoot, name) {
68
49
  const rel = `routing/recipes/${name}.yaml`;
@@ -9,6 +9,11 @@
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
 
12
+ const { read, write, readJson } = require('./sync-fs');
13
+ const { makeAddCheck } = require('./sync-check');
14
+
15
+ const addCheck = makeAddCheck('release-surface');
16
+
12
17
  const LOG_PATH = '.godpowers/surface/RELEASE-SURFACE-SYNC.md';
13
18
 
14
19
  const REQUIRED_PACKAGE_GUARDS = [
@@ -37,26 +42,6 @@ const REQUIRED_RELEASE_TESTS = [
37
42
  'scripts/test-install-smoke.js'
38
43
  ];
39
44
 
40
- function read(projectRoot, relPath) {
41
- const file = path.join(projectRoot, relPath);
42
- if (!fs.existsSync(file)) return '';
43
- return fs.readFileSync(file, 'utf8');
44
- }
45
-
46
- function write(projectRoot, relPath, content) {
47
- const file = path.join(projectRoot, relPath);
48
- fs.mkdirSync(path.dirname(file), { recursive: true });
49
- fs.writeFileSync(file, content);
50
- }
51
-
52
- function readJson(projectRoot, relPath) {
53
- try {
54
- return JSON.parse(read(projectRoot, relPath));
55
- } catch (err) {
56
- return null;
57
- }
58
- }
59
-
60
45
  function releaseGateText(projectRoot, pkg) {
61
46
  return [
62
47
  JSON.stringify((pkg && pkg.scripts) || {}),
@@ -64,18 +49,6 @@ function releaseGateText(projectRoot, pkg) {
64
49
  ].join('\n');
65
50
  }
66
51
 
67
- function addCheck(checks, id, status, relPath, message, opts = {}) {
68
- checks.push({
69
- area: 'release-surface',
70
- id,
71
- status,
72
- path: relPath,
73
- message,
74
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
75
- spawn: opts.spawn || null
76
- });
77
- }
78
-
79
52
  function detect(projectRoot) {
80
53
  const checks = [];
81
54
  const pkg = readJson(projectRoot, 'package.json') || {};
@@ -10,25 +10,10 @@ const path = require('path');
10
10
  const crypto = require('crypto');
11
11
 
12
12
  const pillars = require('./pillars');
13
+ const { read, write, exists } = require('./sync-fs');
13
14
 
14
15
  const LOG_PATH = '.godpowers/docs/REPO-DOC-SYNC.md';
15
16
 
16
- function read(projectRoot, relPath) {
17
- const file = path.join(projectRoot, relPath);
18
- if (!fs.existsSync(file)) return '';
19
- return fs.readFileSync(file, 'utf8');
20
- }
21
-
22
- function write(projectRoot, relPath, text) {
23
- const file = path.join(projectRoot, relPath);
24
- fs.mkdirSync(path.dirname(file), { recursive: true });
25
- fs.writeFileSync(file, text);
26
- }
27
-
28
- function exists(projectRoot, relPath) {
29
- return fs.existsSync(path.join(projectRoot, relPath));
30
- }
31
-
32
17
  function countFiles(projectRoot, dir, pattern) {
33
18
  const full = path.join(projectRoot, dir);
34
19
  if (!fs.existsSync(full)) return 0;
@@ -11,6 +11,8 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
 
13
13
  const { parseSimpleYaml } = require('./intent');
14
+ const { read, write, exists, readJson } = require('./sync-fs');
15
+ const { addCheck, listFiles } = require('./sync-check');
14
16
  const extensions = require('./extensions');
15
17
  const repoDocSync = require('./repo-doc-sync');
16
18
  const routeQualitySync = require('./route-quality-sync');
@@ -51,43 +53,6 @@ const REQUIRED_PACKAGE_CHECKS = [
51
53
  'routing/god-export-otel.yaml'
52
54
  ];
53
55
 
54
- function rel(projectRoot, absPath) {
55
- return path.relative(projectRoot, absPath).split(path.sep).join('/');
56
- }
57
-
58
- function exists(projectRoot, relPath) {
59
- return fs.existsSync(path.join(projectRoot, relPath));
60
- }
61
-
62
- function read(projectRoot, relPath) {
63
- const file = path.join(projectRoot, relPath);
64
- if (!fs.existsSync(file)) return '';
65
- return fs.readFileSync(file, 'utf8');
66
- }
67
-
68
- function write(projectRoot, relPath, content) {
69
- const file = path.join(projectRoot, relPath);
70
- fs.mkdirSync(path.dirname(file), { recursive: true });
71
- fs.writeFileSync(file, content);
72
- }
73
-
74
- function listFiles(projectRoot, relDir, pattern) {
75
- const dir = path.join(projectRoot, relDir);
76
- if (!fs.existsSync(dir)) return [];
77
- return fs.readdirSync(dir)
78
- .filter((name) => pattern.test(name))
79
- .sort()
80
- .map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
81
- }
82
-
83
- function readJson(projectRoot, relPath) {
84
- try {
85
- return JSON.parse(read(projectRoot, relPath));
86
- } catch (err) {
87
- return null;
88
- }
89
- }
90
-
91
56
  function releaseGateText(projectRoot, pkg) {
92
57
  return [
93
58
  JSON.stringify((pkg && pkg.scripts) || {}),
@@ -104,19 +69,6 @@ function commandForSkill(skillPath) {
104
69
  return `/${path.basename(skillPath, '.md')}`;
105
70
  }
106
71
 
107
- function addCheck(checks, area, id, status, relPath, message, opts = {}) {
108
- checks.push({
109
- area,
110
- id,
111
- status,
112
- path: relPath,
113
- message,
114
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
115
- safeFix: opts.safeFix === true,
116
- spawn: opts.spawn || null
117
- });
118
- }
119
-
120
72
  function routingChecks(projectRoot) {
121
73
  const checks = [];
122
74
  const skills = listFiles(projectRoot, 'skills', /^god.*\.md$/);
@@ -508,15 +460,12 @@ function releasePolicyChecks(projectRoot) {
508
460
  checks,
509
461
  'release',
510
462
  'release-checklist-surface-sync',
511
- read(projectRoot, 'docs/RELEASE-CHECKLIST.md').includes('repo-surface-sync'),
463
+ read(projectRoot, 'docs/RELEASE-CHECKLIST.md').includes('repo-surface-sync') ? 'fresh' : 'stale',
512
464
  'docs/RELEASE-CHECKLIST.md',
513
465
  'Release checklist references repo-surface-sync readiness.',
514
466
  { spawn: 'god-docs-writer' }
515
467
  );
516
- return checks.map((check) => ({
517
- ...check,
518
- status: check.status === true ? 'fresh' : (check.status === false ? 'stale' : check.status)
519
- }));
468
+ return checks;
520
469
  }
521
470
 
522
471
  function detect(projectRoot) {
@@ -32,9 +32,12 @@ const path = require('path');
32
32
  const linkage = require('./linkage');
33
33
  const state = require('./state');
34
34
  const atomic = require('./atomic-write');
35
+ const textUtil = require('./text-util');
36
+ const artifactMap = require('./artifact-map');
37
+ const { readTextOrNull: readText } = require('./sync-fs');
35
38
 
36
- const PRD_PATH = '.godpowers/prd/PRD.md';
37
- const ROADMAP_PATH = '.godpowers/roadmap/ROADMAP.md';
39
+ const PRD_PATH = artifactMap.requiredArtifactsForTier('prd')[0].path;
40
+ const ROADMAP_PATH = artifactMap.requiredArtifactsForTier('roadmap')[0].path;
38
41
  const LEDGER_PATH = '.godpowers/REQUIREMENTS.md';
39
42
 
40
43
  const PRIORITIES = ['MUST', 'SHOULD', 'COULD'];
@@ -43,26 +46,12 @@ const REQ_ID_RE_G = /\bP-(MUST|SHOULD|COULD)-\d+\b/g;
43
46
  const MILESTONE_ID_RE = /\bM-[\w-]+\b/;
44
47
  const LABEL_RE = /\[(?:DECISION|HYPOTHESIS|OPEN QUESTION)\]/g;
45
48
 
46
- function readText(projectRoot, relPath) {
47
- const file = path.join(projectRoot, relPath);
48
- if (!fs.existsSync(file)) return null;
49
- try {
50
- return fs.readFileSync(file, 'utf8');
51
- } catch (e) {
52
- return null;
53
- }
54
- }
55
-
56
49
  function pad2(n) {
57
50
  return String(n).padStart(2, '0');
58
51
  }
59
52
 
60
53
  function slugify(text) {
61
- return String(text)
62
- .toLowerCase()
63
- .replace(/[^a-z0-9]+/g, '-')
64
- .replace(/^-+|-+$/g, '')
65
- .slice(0, 40) || 'increment';
54
+ return textUtil.slugify(text, 'increment');
66
55
  }
67
56
 
68
57
  // ============================================================================
@@ -318,23 +318,29 @@ function run(projectRoot, opts = {}) {
318
318
  // write the file when the PRD actually declares requirements, to avoid
319
319
  // littering pre-PRD projects with an empty ledger.
320
320
  let requirementsSummary = null;
321
+ let requirementsError = null;
321
322
  if (opts.runRequirements !== false) {
322
323
  try {
323
324
  const derived = requirements.derive(projectRoot);
324
325
  if (derived.hasRequirements) {
325
- requirements.writeLedger(projectRoot, derived);
326
326
  const currentState = state.read(projectRoot);
327
327
  requirementsSummary = requirements.summarizeForState(
328
328
  derived,
329
329
  currentState && currentState.deliverables
330
330
  );
331
+ // Write state.json first, then the ledger, so a state-write failure
332
+ // cannot leave a REQUIREMENTS.md ledger that state.json never references.
331
333
  if (currentState) {
332
334
  currentState.deliverables = requirementsSummary;
333
335
  state.write(projectRoot, currentState);
334
336
  }
337
+ requirements.writeLedger(projectRoot, derived);
335
338
  }
336
339
  } catch (e) {
340
+ // ERR-001: surface the failure instead of swallowing it, so the caller can
341
+ // tell a genuine requirements-step error apart from "no requirements".
337
342
  requirementsSummary = null;
343
+ requirementsError = e.message;
338
344
  }
339
345
  }
340
346
 
@@ -346,7 +352,8 @@ function run(projectRoot, opts = {}) {
346
352
  footers,
347
353
  sourceSyncResult,
348
354
  reviewItems,
349
- requirements: requirementsSummary
355
+ requirements: requirementsSummary,
356
+ requirementsError
350
357
  };
351
358
  }
352
359
 
@@ -10,6 +10,10 @@ const fs = require('fs');
10
10
  const path = require('path');
11
11
 
12
12
  const { parseSimpleYaml } = require('./intent');
13
+ const { read, write } = require('./sync-fs');
14
+ const { makeAddCheck, listFiles } = require('./sync-check');
15
+
16
+ const addCheck = makeAddCheck('route-quality');
13
17
 
14
18
  const LOG_PATH = '.godpowers/surface/ROUTE-QUALITY-SYNC.md';
15
19
  const CONTEXTUAL_NEXT_VALUES = new Set([
@@ -100,27 +104,6 @@ const TIER_GATE_COMMANDS = new Set([
100
104
  '/god-harden'
101
105
  ]);
102
106
 
103
- function read(projectRoot, relPath) {
104
- const file = path.join(projectRoot, relPath);
105
- if (!fs.existsSync(file)) return '';
106
- return fs.readFileSync(file, 'utf8');
107
- }
108
-
109
- function write(projectRoot, relPath, content) {
110
- const file = path.join(projectRoot, relPath);
111
- fs.mkdirSync(path.dirname(file), { recursive: true });
112
- fs.writeFileSync(file, content);
113
- }
114
-
115
- function listFiles(projectRoot, relDir, pattern) {
116
- const dir = path.join(projectRoot, relDir);
117
- if (!fs.existsSync(dir)) return [];
118
- return fs.readdirSync(dir)
119
- .filter((name) => pattern.test(name))
120
- .sort()
121
- .map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
122
- }
123
-
124
107
  function arr(value) {
125
108
  return Array.isArray(value) ? value : [];
126
109
  }
@@ -133,18 +116,6 @@ function parseRoute(projectRoot, routePath) {
133
116
  }
134
117
  }
135
118
 
136
- function addCheck(checks, id, status, relPath, message, opts = {}) {
137
- checks.push({
138
- area: 'route-quality',
139
- id,
140
- status,
141
- path: relPath,
142
- message,
143
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
144
- spawn: opts.spawn || null
145
- });
146
- }
147
-
148
119
  function spawnTokens(route) {
149
120
  const execution = route.execution || {};
150
121
  return normalizeSpawnList([
@@ -30,10 +30,6 @@ const SYSTEM_TARGETS = {
30
30
  }
31
31
  };
32
32
 
33
- function rel(projectRoot, absPath) {
34
- return path.relative(projectRoot, absPath).split(path.sep).join('/');
35
- }
36
-
37
33
  function sha(input) {
38
34
  return `sha256:${crypto.createHash('sha256').update(input).digest('hex')}`;
39
35
  }
package/lib/state.js CHANGED
@@ -53,10 +53,26 @@ const SUBSTEP_LABELS = {
53
53
  * @property {number} ordinal One-based step position.
54
54
  */
55
55
 
56
+ // Canonical project-relative location of the state file. Other modules that
57
+ // need to name state.json (gates, dispatch findings, audits) import this rather
58
+ // than re-typing the literal (ARC-002).
59
+ const STATE_FILE = '.godpowers/state.json';
60
+
56
61
  function statePath(projectRoot) {
57
62
  return path.join(projectRoot, '.godpowers', 'state.json');
58
63
  }
59
64
 
65
+ // A typed error so callers (e.g. the CLI dispatcher) can detect corrupt state
66
+ // by `err.code === 'CORRUPT_STATE'` instead of matching the message prose.
67
+ function corruptStateError(file, cause) {
68
+ const err = new Error(
69
+ `Corrupt state file at ${file}: ${cause.message}. ` +
70
+ `Fix the JSON or remove the file to let Godpowers reinitialize it.`
71
+ );
72
+ err.code = 'CORRUPT_STATE';
73
+ return err;
74
+ }
75
+
60
76
  function tierNumber(tierKey) {
61
77
  const match = String(tierKey).match(/^tier-(\d+)$/);
62
78
  return match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
@@ -88,10 +104,7 @@ function read(projectRoot) {
88
104
  try {
89
105
  return JSON.parse(raw);
90
106
  } catch (e) {
91
- throw new Error(
92
- `Corrupt state file at ${file}: ${e.message}. ` +
93
- `Fix the JSON or remove the file to let Godpowers reinitialize it.`
94
- );
107
+ throw corruptStateError(file, e);
95
108
  }
96
109
  }
97
110
 
@@ -144,10 +157,7 @@ async function readAsync(projectRoot) {
144
157
  try {
145
158
  return JSON.parse(raw);
146
159
  } catch (e) {
147
- throw new Error(
148
- `Corrupt state file at ${file}: ${e.message}. ` +
149
- `Fix the JSON or remove the file to let Godpowers reinitialize it.`
150
- );
160
+ throw corruptStateError(file, e);
151
161
  }
152
162
  }
153
163
 
@@ -421,6 +431,7 @@ module.exports = {
421
431
  updateSubStepAsync,
422
432
  hashFile,
423
433
  detectDrift,
434
+ STATE_FILE,
424
435
  statePath,
425
436
  isInitialized,
426
437
  isInitializedState,
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Shared check-builder and file-lister for the lib/*-sync.js family (ARC-001).
3
+ *
4
+ * The aggregator (repo-surface-sync) passes a per-call `area` and may mark a
5
+ * check `safeFix`, so it uses the full `addCheck`. The single-area sync modules
6
+ * (recipe-coverage, release-surface, route-quality) bind their area once via
7
+ * `makeAddCheck(area)`; their records intentionally omit `safeFix` (none of
8
+ * their checks are auto-fixable), matching the original per-module builders.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ function severityFor(status, opts) {
15
+ return opts.severity || (status === 'fresh' ? 'info' : 'warning');
16
+ }
17
+
18
+ // Full form: caller supplies `area`; records include the `safeFix` flag.
19
+ function addCheck(checks, area, id, status, relPath, message, opts = {}) {
20
+ checks.push({
21
+ area,
22
+ id,
23
+ status,
24
+ path: relPath,
25
+ message,
26
+ severity: severityFor(status, opts),
27
+ safeFix: opts.safeFix === true,
28
+ spawn: opts.spawn || null
29
+ });
30
+ }
31
+
32
+ // Area-bound form for single-area modules; records omit `safeFix`.
33
+ function makeAddCheck(area) {
34
+ return function (checks, id, status, relPath, message, opts = {}) {
35
+ checks.push({
36
+ area,
37
+ id,
38
+ status,
39
+ path: relPath,
40
+ message,
41
+ severity: severityFor(status, opts),
42
+ spawn: opts.spawn || null
43
+ });
44
+ };
45
+ }
46
+
47
+ function listFiles(projectRoot, relDir, pattern) {
48
+ const dir = path.join(projectRoot, relDir);
49
+ if (!fs.existsSync(dir)) return [];
50
+ return fs.readdirSync(dir)
51
+ .filter((name) => pattern.test(name))
52
+ .sort()
53
+ .map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
54
+ }
55
+
56
+ module.exports = { addCheck, makeAddCheck, listFiles };
package/lib/sync-fs.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared filesystem helpers for the lib/*-sync.js family.
3
+ *
4
+ * Every sync module used to redefine its own byte-identical read/write/exists/
5
+ * readJson against a project root (ARC-001). They now share these so a change
6
+ * to path handling or read semantics lives in one place. Module-specific log
7
+ * writers (appendLog) stay per-module because their headers and formats differ.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ function read(projectRoot, relPath) {
14
+ const file = path.join(projectRoot, relPath);
15
+ if (!fs.existsSync(file)) return '';
16
+ return fs.readFileSync(file, 'utf8');
17
+ }
18
+
19
+ function write(projectRoot, relPath, content) {
20
+ const file = path.join(projectRoot, relPath);
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ fs.writeFileSync(file, content);
23
+ }
24
+
25
+ function exists(projectRoot, relPath) {
26
+ return fs.existsSync(path.join(projectRoot, relPath));
27
+ }
28
+
29
+ function readJson(projectRoot, relPath) {
30
+ try {
31
+ return JSON.parse(read(projectRoot, relPath));
32
+ } catch (err) {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ // Like read(), but returns null (not '') when the file is missing or unreadable,
38
+ // for callers that distinguish "absent" from "empty".
39
+ function readTextOrNull(projectRoot, relPath) {
40
+ const file = path.join(projectRoot, relPath);
41
+ if (!fs.existsSync(file)) return null;
42
+ try {
43
+ return fs.readFileSync(file, 'utf8');
44
+ } catch (err) {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ module.exports = { read, write, exists, readJson, readTextOrNull };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Small shared string helpers (QUAL-002).
3
+ *
4
+ * slugify is the canonical home for the "lowercase, collapse non-alphanumerics
5
+ * to '-', strip edge '-', truncate to 40 chars" contract. lib/evidence.js keeps
6
+ * its own copy on purpose: that module is vendored from the upstream engine and
7
+ * its helpers are provenance-tracked, so it must not import first-party code.
8
+ */
9
+
10
+ function slugify(text, fallback = '') {
11
+ const slug = String(text == null ? '' : text)
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9]+/g, '-')
14
+ .replace(/^-+|-+$/g, '')
15
+ .slice(0, 40);
16
+ return slug || fallback;
17
+ }
18
+
19
+ module.exports = { slugify };
@@ -6,6 +6,10 @@
6
6
  */
7
7
 
8
8
  const HELPER_GROUPS = {
9
+ 'context-bootstrap': [
10
+ 'pillars-detect',
11
+ 'pillars-init'
12
+ ],
9
13
  'standard-closeout': [
10
14
  'repo-doc-sync',
11
15
  'repo-surface-sync',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "godpowers",
3
- "version": "3.13.0",
3
+ "version": "3.13.2",
4
4
  "description": "AI-powered development system: 120 slash commands and 40 specialist agents that take a project from raw idea to hardened production. Runs inside Claude Code, Codex, Cursor, Windsurf, Gemini, and 10+ other AI coding tools.",
5
5
  "bin": {
6
6
  "godpowers": "./bin/install.js"
@@ -24,11 +24,11 @@
24
24
  "test:e2e": "node tests/integration/full-arc.test.js",
25
25
  "test:mcp": "npm --workspace @godpowers/mcp test",
26
26
  "coverage": "c8 --reporter=text --reporter=lcov node scripts/run-tests.js",
27
- "coverage:lib": "c8 --include=lib/**/*.js --check-coverage --lines 90 --reporter=text node scripts/run-tests.js",
27
+ "coverage:lib": "c8 --include=lib/**/*.js --check-coverage --lines 90 --branches 75 --reporter=text --reporter=json-summary node scripts/run-tests.js",
28
28
  "test:audit": "npm audit --omit=dev && git diff --check && npm run test:surface",
29
29
  "pack:check": "node scripts/check-package-contents.js",
30
30
  "pack:mcp:check": "npm --workspace @godpowers/mcp run pack:check",
31
- "release:check": "npm run coverage:lib && npm run test:audit && npm run pack:check && npm run pack:mcp:check",
31
+ "release:check": "npm run coverage:lib && node scripts/check-per-file-coverage.js && npm run test:audit && npm run pack:check && npm run pack:mcp:check",
32
32
  "lint": "node scripts/static-check.js"
33
33
  },
34
34
  "workspaces": [
@@ -115,6 +115,13 @@ This converts existing Godpowers artifacts into managed source references in
115
115
  the relevant pillar files, so old projects are Pillar-ized as part of being
116
116
  Godpower-ized.
117
117
 
118
+ In the greenfield `full-arc` workflow this start-of-arc step is surfaced as the
119
+ tier-0 `context` job, whose `context-bootstrap` helper group expands to
120
+ `pillars-detect` (`lib/pillars.detect`) and `pillars-init` (`lib/pillars.init`).
121
+ The job uses `god-orchestrator` as a local runtime call, not a `god-context-writer`
122
+ spawn, so it changes nothing about the behavior described above; it only makes the
123
+ init visible in `/god-mode --plan` alongside the closeout `pillars-sync-plan`.
124
+
118
125
  Before each major command, compute the task-specific Pillars load set with
119
126
  `lib/pillars.computeLoadSet(projectRoot, taskText)`. Load `agents/context.md`
120
127
  and `agents/repo.md` first, then the routed primary pillars and their direct