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/CHANGELOG.md +97 -0
- package/README.md +33 -25
- package/RELEASE.md +20 -21
- package/bin/install.js +1 -16
- package/hooks/pre-tool-use.sh +52 -40
- package/lib/README.md +12 -1
- package/lib/artifact-map.js +6 -0
- package/lib/cli-dispatch.js +29 -20
- package/lib/cli-log.js +24 -0
- package/lib/dashboard.js +1 -10
- package/lib/evidence.js +54 -13
- package/lib/gate.js +2 -2
- package/lib/have-nots-validator.js +5 -1
- package/lib/installer-args.js +140 -290
- package/lib/installer-core.js +1 -12
- package/lib/intent.js +9 -4
- package/lib/pillars.js +13 -0
- package/lib/planning-systems.js +2 -10
- package/lib/recipe-coverage-sync.js +4 -23
- package/lib/release-surface-sync.js +5 -32
- package/lib/repo-doc-sync.js +1 -16
- package/lib/repo-surface-sync.js +4 -55
- package/lib/requirements.js +6 -17
- package/lib/reverse-sync.js +9 -2
- package/lib/route-quality-sync.js +4 -33
- package/lib/source-sync.js +0 -4
- package/lib/state.js +19 -8
- package/lib/sync-check.js +56 -0
- package/lib/sync-fs.js +49 -0
- package/lib/text-util.js +19 -0
- package/lib/workflow-helper-groups.js +4 -0
- package/package.json +3 -3
- package/references/orchestration/GOD-ORCHESTRATOR-RUNBOOK.md +7 -0
- package/workflows/full-arc.yaml +18 -0
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 = [];
|
package/lib/planning-systems.js
CHANGED
|
@@ -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') || {};
|
package/lib/repo-doc-sync.js
CHANGED
|
@@ -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;
|
package/lib/repo-surface-sync.js
CHANGED
|
@@ -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
|
|
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) {
|
package/lib/requirements.js
CHANGED
|
@@ -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 = '
|
|
37
|
-
const ROADMAP_PATH = '
|
|
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
|
|
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
|
// ============================================================================
|
package/lib/reverse-sync.js
CHANGED
|
@@ -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([
|
package/lib/source-sync.js
CHANGED
|
@@ -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
|
|
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
|
|
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 };
|
package/lib/text-util.js
ADDED
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "godpowers",
|
|
3
|
-
"version": "3.13.
|
|
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
|