godpowers 2.0.0 → 2.1.1
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/AGENTS.md +1 -1
- package/CHANGELOG.md +141 -0
- package/README.md +45 -5
- package/RELEASE.md +30 -48
- package/SKILL.md +9 -1
- package/agents/god-design-reviewer.md +6 -6
- package/agents/god-designer.md +1 -1
- package/agents/god-executor.md +23 -0
- package/agents/god-quality-reviewer.md +12 -1
- package/agents/god-spec-reviewer.md +10 -0
- package/bin/install.js +119 -655
- package/extensions/launch-pack/README.md +1 -1
- package/lib/README.md +16 -0
- package/lib/agent-browser-driver.js +13 -13
- package/lib/agent-cache.js +8 -1
- package/lib/agent-refs.js +161 -0
- package/lib/budget.js +25 -11
- package/lib/context-writer.js +17 -6
- package/lib/events.js +11 -4
- package/lib/extension-authoring.js +27 -0
- package/lib/feature-awareness.js +18 -0
- package/lib/fs-async.js +28 -0
- package/lib/installer-args.js +99 -0
- package/lib/installer-core.js +345 -0
- package/lib/installer-files.js +80 -0
- package/lib/installer-runtimes.js +112 -0
- package/lib/intent.js +111 -16
- package/lib/release-surface-sync.js +8 -1
- package/lib/repo-surface-sync.js +9 -2
- package/lib/review-required.js +2 -1
- package/lib/router.js +23 -3
- package/lib/skill-surface.js +42 -0
- package/lib/state-lock.js +10 -0
- package/lib/state.js +101 -8
- package/lib/workflow-runner.js +42 -5
- package/package.json +4 -3
- package/references/HAVE-NOTS.md +4 -3
- package/references/orchestration/GOD-MODE-RUNBOOK.md +273 -0
- package/routing/god-arch.yaml +1 -1
- package/routing/god-build.yaml +1 -1
- package/skills/god-add-backlog.md +1 -1
- package/skills/god-agent-audit.md +2 -2
- package/skills/god-build.md +5 -3
- package/skills/god-context-scan.md +2 -3
- package/skills/god-design.md +2 -2
- package/skills/god-doctor.md +2 -2
- package/skills/god-help.md +4 -3
- package/skills/god-mode.md +10 -266
- package/skills/god-org-context.md +1 -1
- package/skills/god-repair.md +3 -3
- package/skills/god-review.md +9 -0
- package/skills/god-stories.md +1 -1
- package/skills/god-version.md +2 -2
|
@@ -11,7 +11,7 @@ Channel-specific launch strategists for Godpowers.
|
|
|
11
11
|
| `/god-indie-hackers` | god-indie-hackers-strategist | Numbers-first, mistakes, real questions |
|
|
12
12
|
| `/god-oss-release` | god-oss-release-strategist | README, SemVer, examples that run |
|
|
13
13
|
|
|
14
|
-
Plus 4 workflows and
|
|
14
|
+
Plus 4 workflows and 23 channel-specific have-nots.
|
|
15
15
|
|
|
16
16
|
## When to use
|
|
17
17
|
|
package/lib/README.md
CHANGED
|
@@ -22,6 +22,7 @@ package-level integrations.
|
|
|
22
22
|
| `dogfood-runner.js` | Run deterministic messy-repo scenarios against migration, host, extension, and suite release behavior. |
|
|
23
23
|
| `budget.js` | Read and enforce configured budget controls. |
|
|
24
24
|
| `cost-tracker.js` | Track token and cost estimates from event streams. |
|
|
25
|
+
| `fs-async.js` | Promise-based file read/write helpers for non-blocking runtime paths. |
|
|
25
26
|
|
|
26
27
|
## Events and observability
|
|
27
28
|
|
|
@@ -43,6 +44,8 @@ package-level integrations.
|
|
|
43
44
|
| `workflow-runner.js` | Execute workflow steps with validation hooks. |
|
|
44
45
|
| `agent-cache.js` | Cache agent metadata for faster routing. |
|
|
45
46
|
| `agent-validator.js` | Validate agent frontmatter and contracts. |
|
|
47
|
+
| `agent-refs.js` | Validate workflow agent references and scan skill/agent prose for phantom references. |
|
|
48
|
+
| `skill-surface.js` | Derive slash-command metadata from the individual `skills/` files. |
|
|
46
49
|
|
|
47
50
|
## Artifact quality
|
|
48
51
|
|
|
@@ -71,6 +74,7 @@ package-level integrations.
|
|
|
71
74
|
| `impeccable-bridge.js` | Bridge runtime checks into impeccable quality workflows. |
|
|
72
75
|
| `extensions.js` | Load and validate extension packs. |
|
|
73
76
|
| `extension-authoring.js` | Scaffold publishable extension packs with manifest, package, README, skill, agent, and workflow files. |
|
|
77
|
+
| `pillars.js` | Manage the Pillars project-context layer (`AGENTS.md` plus routed `agents/*.md`). |
|
|
74
78
|
|
|
75
79
|
## Repository and graph helpers
|
|
76
80
|
|
|
@@ -87,5 +91,17 @@ package-level integrations.
|
|
|
87
91
|
| `review-required.js` | Decide when review gates should block progress. |
|
|
88
92
|
| `suite-state.js` | Manage state across registered project suites. |
|
|
89
93
|
|
|
94
|
+
## Installer, dashboard, and CLI helpers
|
|
95
|
+
|
|
96
|
+
| Module | Purpose |
|
|
97
|
+
|--------|---------|
|
|
98
|
+
| `installer-core.js` | Install and uninstall the Godpowers surface for each runtime. |
|
|
99
|
+
| `installer-files.js` | File-copy helpers shared by the installer and its tests. |
|
|
100
|
+
| `installer-args.js` | Parse `bin/install.js` arguments and subcommands. |
|
|
101
|
+
| `installer-runtimes.js` | Map supported runtimes to their config directories. |
|
|
102
|
+
| `automation-providers.js` | Detect and configure host-native automation providers. |
|
|
103
|
+
| `dashboard.js` | Compute the next-step action brief and host guarantee line. |
|
|
104
|
+
| `quick-proof.js` | Render the shipped proof fixture for `godpowers quick-proof`. |
|
|
105
|
+
|
|
90
106
|
See `../ARCHITECTURE.md` for system design and `../docs/ROADMAP.md` for planned
|
|
91
107
|
runtime work.
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* isInstalled() -> bool
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
const {
|
|
33
|
+
const { execFileSync } = require('child_process');
|
|
34
34
|
const path = require('path');
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -38,14 +38,14 @@ const path = require('path');
|
|
|
38
38
|
*/
|
|
39
39
|
function isInstalled() {
|
|
40
40
|
try {
|
|
41
|
-
|
|
41
|
+
execFileSync('agent-browser', ['--version'], {
|
|
42
42
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
43
|
timeout: 5000
|
|
44
44
|
});
|
|
45
45
|
return true;
|
|
46
46
|
} catch (e1) {
|
|
47
47
|
try {
|
|
48
|
-
|
|
48
|
+
execFileSync('npx', ['--no-install', 'agent-browser', '--version'], {
|
|
49
49
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
50
50
|
timeout: 5000
|
|
51
51
|
});
|
|
@@ -59,21 +59,21 @@ function isInstalled() {
|
|
|
59
59
|
/**
|
|
60
60
|
* Run an agent-browser CLI command and return stdout.
|
|
61
61
|
* Throws on non-zero exit. Times out after 30s default.
|
|
62
|
+
*
|
|
63
|
+
* Arguments are passed as an argv array with the shell disabled. URLs,
|
|
64
|
+
* selectors, and eval expressions can originate from project content
|
|
65
|
+
* (PRD/DESIGN acceptance criteria, CLI flags), so they must never be
|
|
66
|
+
* concatenated into a shell command string where metacharacters like
|
|
67
|
+
* `;`, `&&`, `$()`, or backticks could trigger command injection.
|
|
62
68
|
*/
|
|
63
69
|
function run(args, opts = {}) {
|
|
64
|
-
const cmd = Array.isArray(args) ? args : [args];
|
|
65
|
-
|
|
66
|
-
const argString = cmd.map(a => {
|
|
67
|
-
const s = String(a);
|
|
68
|
-
if (/\s/.test(s) && !s.startsWith('"')) return `"${s.replace(/"/g, '\\"')}"`;
|
|
69
|
-
return s;
|
|
70
|
-
}).join(' ');
|
|
71
|
-
const fullCmd = `agent-browser ${argString}`;
|
|
72
|
-
return execSync(fullCmd, {
|
|
70
|
+
const cmd = (Array.isArray(args) ? args : [args]).map(String);
|
|
71
|
+
return execFileSync('agent-browser', cmd, {
|
|
73
72
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
74
73
|
timeout: opts.timeout || 30000,
|
|
75
74
|
cwd: opts.cwd || process.cwd(),
|
|
76
|
-
encoding: 'utf8'
|
|
75
|
+
encoding: 'utf8',
|
|
76
|
+
shell: false
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
|
package/lib/agent-cache.js
CHANGED
|
@@ -139,7 +139,14 @@ function clear(projectRoot, opts = {}) {
|
|
|
139
139
|
const fpath = path.join(shardPath, fname);
|
|
140
140
|
let entry;
|
|
141
141
|
try { entry = JSON.parse(fs.readFileSync(fpath, 'utf8')); }
|
|
142
|
-
catch (e) {
|
|
142
|
+
catch (e) {
|
|
143
|
+
// Corrupt or non-JSON entry: prune it only on an explicit full clear.
|
|
144
|
+
// A narrow clear (by agent, expiry, or age) must not delete unrelated
|
|
145
|
+
// unparseable files it was never asked to touch.
|
|
146
|
+
if (opts.all) { fs.unlinkSync(fpath); removed++; }
|
|
147
|
+
else { kept++; }
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
143
150
|
let shouldRemove = false;
|
|
144
151
|
if (opts.all) shouldRemove = true;
|
|
145
152
|
else if (opts.agent && entry.agent === opts.agent) shouldRemove = true;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { isCompatible, parseManifest } = require('./extensions');
|
|
4
|
+
|
|
5
|
+
const AGENT_CONTRACT_VERSION = '1.0.0';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} AgentRef
|
|
9
|
+
* @property {string|null} agent Agent name without the range suffix.
|
|
10
|
+
* @property {string|null} range SemVer range declared after the final at sign.
|
|
11
|
+
* @property {string} raw Original workflow `uses` value.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {AgentRef & { contractVersion: string, valid: boolean, errors: string[] }} AgentRefValidation
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} ref
|
|
20
|
+
* @returns {AgentRef}
|
|
21
|
+
*/
|
|
22
|
+
function parseAgentRef(ref) {
|
|
23
|
+
if (!ref) return { agent: null, range: null, raw: ref };
|
|
24
|
+
const raw = String(ref).trim();
|
|
25
|
+
const at = raw.lastIndexOf('@');
|
|
26
|
+
if (at <= 0) {
|
|
27
|
+
return { agent: raw, range: null, raw };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
agent: raw.slice(0, at),
|
|
31
|
+
range: raw.slice(at + 1),
|
|
32
|
+
raw
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} ref
|
|
38
|
+
* @param {string} [contractVersion]
|
|
39
|
+
* @returns {AgentRefValidation}
|
|
40
|
+
*/
|
|
41
|
+
function validateAgentRef(ref, contractVersion = AGENT_CONTRACT_VERSION) {
|
|
42
|
+
const parsed = parseAgentRef(ref);
|
|
43
|
+
const errors = [];
|
|
44
|
+
|
|
45
|
+
if (!parsed.agent || !/^[a-z][a-z0-9-]*$/.test(parsed.agent)) {
|
|
46
|
+
errors.push(`invalid agent name in uses: ${ref}`);
|
|
47
|
+
}
|
|
48
|
+
if (!parsed.range) {
|
|
49
|
+
errors.push(`agent ref must include a semver range: ${ref}`);
|
|
50
|
+
} else if (!isCompatible(parsed.range, contractVersion)) {
|
|
51
|
+
errors.push(`agent ref ${ref} does not satisfy agent contract ${contractVersion}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { ...parsed, contractVersion, valid: errors.length === 0, errors };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} ref
|
|
59
|
+
* @param {string} [contractVersion]
|
|
60
|
+
* @returns {AgentRefValidation}
|
|
61
|
+
*/
|
|
62
|
+
function assertAgentRef(ref, contractVersion = AGENT_CONTRACT_VERSION) {
|
|
63
|
+
const result = validateAgentRef(ref, contractVersion);
|
|
64
|
+
if (!result.valid) {
|
|
65
|
+
throw new Error(result.errors.join('; '));
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {Object} ProseRef
|
|
72
|
+
* @property {string} token Unresolved god-* token (leading slash stripped).
|
|
73
|
+
* @property {string} file Repo-relative path where the token was found.
|
|
74
|
+
* @property {number} count Occurrences of that token in that file.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
const PROSE_TOKEN = /(?<![A-Za-z0-9_])\/?god-[a-z0-9]+(?:-[a-z0-9]+)*/g;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Collect the set of every god-* command/agent name that legitimately exists:
|
|
81
|
+
* core skills, core agents, and the skills/agents provided by first-party
|
|
82
|
+
* extension packs. {god, godpowers} are always valid (front door + binary).
|
|
83
|
+
*
|
|
84
|
+
* @param {string} rootDir Repository root.
|
|
85
|
+
* @returns {Set<string>}
|
|
86
|
+
*/
|
|
87
|
+
function knownGodNames(rootDir) {
|
|
88
|
+
const valid = new Set(['god', 'godpowers']);
|
|
89
|
+
|
|
90
|
+
for (const f of fs.readdirSync(path.join(rootDir, 'skills'))) {
|
|
91
|
+
if (f.endsWith('.md')) valid.add(f.replace(/\.md$/, ''));
|
|
92
|
+
}
|
|
93
|
+
for (const f of fs.readdirSync(path.join(rootDir, 'agents'))) {
|
|
94
|
+
if (/^god-.*\.md$/.test(f)) valid.add(f.replace(/\.md$/, ''));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const extDir = path.join(rootDir, 'extensions');
|
|
98
|
+
if (fs.existsSync(extDir)) {
|
|
99
|
+
for (const pack of fs.readdirSync(extDir)) {
|
|
100
|
+
const manifestPath = path.join(extDir, pack, 'manifest.yaml');
|
|
101
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
102
|
+
let provides = {};
|
|
103
|
+
try {
|
|
104
|
+
const parsed = parseManifest(fs.readFileSync(manifestPath, 'utf8'));
|
|
105
|
+
provides = (parsed && parsed.manifest && parsed.manifest.provides) || {};
|
|
106
|
+
} catch (e) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
for (const list of [provides.skills, provides.agents]) {
|
|
110
|
+
if (Array.isArray(list)) {
|
|
111
|
+
for (const name of list) valid.add(String(name).trim());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return valid;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Scan skill and agent prose for `god-*` references that do not resolve to a
|
|
122
|
+
* real core skill, core agent, or extension-provided skill/agent. Catches
|
|
123
|
+
* phantom references that the workflow `uses:` validator cannot see because
|
|
124
|
+
* they live in markdown bodies rather than YAML.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} rootDir Repository root.
|
|
127
|
+
* @returns {ProseRef[]} Unresolved references (empty when everything resolves).
|
|
128
|
+
*/
|
|
129
|
+
function findUnresolvedProseRefs(rootDir) {
|
|
130
|
+
const valid = knownGodNames(rootDir);
|
|
131
|
+
const unresolved = [];
|
|
132
|
+
|
|
133
|
+
for (const dir of ['skills', 'agents']) {
|
|
134
|
+
const dirPath = path.join(rootDir, dir);
|
|
135
|
+
for (const f of fs.readdirSync(dirPath)) {
|
|
136
|
+
if (!f.endsWith('.md')) continue;
|
|
137
|
+
const text = fs.readFileSync(path.join(dirPath, f), 'utf8');
|
|
138
|
+
const counts = new Map();
|
|
139
|
+
let m;
|
|
140
|
+
while ((m = PROSE_TOKEN.exec(text)) !== null) {
|
|
141
|
+
const token = m[0].replace(/^\//, '');
|
|
142
|
+
if (valid.has(token)) continue;
|
|
143
|
+
counts.set(token, (counts.get(token) || 0) + 1);
|
|
144
|
+
}
|
|
145
|
+
for (const [token, count] of counts) {
|
|
146
|
+
unresolved.push({ token, file: `${dir}/${f}`, count });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return unresolved;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
AGENT_CONTRACT_VERSION,
|
|
156
|
+
parseAgentRef,
|
|
157
|
+
validateAgentRef,
|
|
158
|
+
assertAgentRef,
|
|
159
|
+
knownGodNames,
|
|
160
|
+
findUnresolvedProseRefs
|
|
161
|
+
};
|
package/lib/budget.js
CHANGED
|
@@ -75,19 +75,32 @@ function writeBlock(projectRoot, newBudgets) {
|
|
|
75
75
|
*/
|
|
76
76
|
function stripBudgets(raw) {
|
|
77
77
|
const lines = raw.split('\n');
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
const range = findTopLevelBlock(lines, 'budgets');
|
|
79
|
+
if (!range) return raw;
|
|
80
|
+
const out = lines.slice(0, range.start).concat(lines.slice(range.end));
|
|
81
|
+
return out.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function findTopLevelBlock(lines, key) {
|
|
85
|
+
const header = new RegExp(`^${key}\\s*:(?:\\s*(?:#.*)?)?$`);
|
|
86
|
+
let start = -1;
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
if (header.test(lines[i])) {
|
|
89
|
+
start = i;
|
|
90
|
+
break;
|
|
87
91
|
}
|
|
88
|
-
out.push(line);
|
|
89
92
|
}
|
|
90
|
-
|
|
93
|
+
if (start === -1) return null;
|
|
94
|
+
|
|
95
|
+
let end = lines.length;
|
|
96
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
97
|
+
if (!lines[i].trim()) continue;
|
|
98
|
+
if (/^\S/.test(lines[i])) {
|
|
99
|
+
end = i;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { start, end };
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
/**
|
|
@@ -211,5 +224,6 @@ module.exports = {
|
|
|
211
224
|
writeBlock,
|
|
212
225
|
stripBudgets,
|
|
213
226
|
renderBudgets,
|
|
227
|
+
findTopLevelBlock,
|
|
214
228
|
DEFAULTS
|
|
215
229
|
};
|
package/lib/context-writer.js
CHANGED
|
@@ -216,15 +216,21 @@ function writeFenced(filePath, sectionContent) {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
|
-
* Remove the Godpowers fence from a file. Leaves the rest untouched. If
|
|
220
|
-
*
|
|
219
|
+
* Remove the Godpowers fence from a file. Leaves the rest untouched. If only
|
|
220
|
+
* the fence remained, an auto-generated pointer file is deleted; with
|
|
221
|
+
* { preserveFile: true } the file is emptied instead of deleted (used for the
|
|
222
|
+
* canonical AGENTS.md so the off-switch never removes the user's primary file).
|
|
221
223
|
*/
|
|
222
|
-
function removeFenced(filePath) {
|
|
224
|
+
function removeFenced(filePath, opts = {}) {
|
|
223
225
|
if (!fs.existsSync(filePath)) return { removed: false, reason: 'missing' };
|
|
224
226
|
const parsed = readFenced(filePath);
|
|
225
227
|
if (parsed.fenced === '') return { removed: false, reason: 'no-fence' };
|
|
226
228
|
let remaining = `${parsed.before}${parsed.after}`.replace(/\n{3,}/g, '\n\n').trim();
|
|
227
229
|
if (remaining === '') {
|
|
230
|
+
if (opts.preserveFile) {
|
|
231
|
+
fs.writeFileSync(filePath, '');
|
|
232
|
+
return { removed: true, fileDeleted: false, emptied: true };
|
|
233
|
+
}
|
|
228
234
|
fs.unlinkSync(filePath);
|
|
229
235
|
return { removed: true, fileDeleted: true };
|
|
230
236
|
}
|
|
@@ -284,8 +290,11 @@ function apply(projectRoot, state, opts = {}) {
|
|
|
284
290
|
* Remove all Godpowers fences from canonical + pointers (off-switch).
|
|
285
291
|
*/
|
|
286
292
|
function clearAll(projectRoot) {
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
// The canonical AGENTS.md is the user's primary context file: empty it rather
|
|
294
|
+
// than delete it. The remaining targets are auto-generated pointer files that
|
|
295
|
+
// Godpowers owns outright, so they are deleted when only the fence remains.
|
|
296
|
+
const canonical = path.join(projectRoot, 'AGENTS.md');
|
|
297
|
+
const pointers = [
|
|
289
298
|
path.join(projectRoot, 'CLAUDE.md'),
|
|
290
299
|
path.join(projectRoot, 'GEMINI.md'),
|
|
291
300
|
path.join(projectRoot, '.cursorrules'),
|
|
@@ -300,7 +309,9 @@ function clearAll(projectRoot) {
|
|
|
300
309
|
path.join(projectRoot, '.agents', 'skills', 'godpowers.md')
|
|
301
310
|
];
|
|
302
311
|
const results = [];
|
|
303
|
-
|
|
312
|
+
const rc = removeFenced(canonical, { preserveFile: true });
|
|
313
|
+
if (rc.removed) results.push({ path: canonical, ...rc });
|
|
314
|
+
for (const t of pointers) {
|
|
304
315
|
const r = removeFenced(t);
|
|
305
316
|
if (r.removed) results.push({ path: t, ...r });
|
|
306
317
|
}
|
package/lib/events.js
CHANGED
|
@@ -161,10 +161,17 @@ function verifyChain(file) {
|
|
|
161
161
|
function readRun(projectRoot, runId) {
|
|
162
162
|
const file = eventsPath(projectRoot, runId);
|
|
163
163
|
if (!fs.existsSync(file)) return [];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
const out = [];
|
|
165
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
166
|
+
if (!line) continue;
|
|
167
|
+
try {
|
|
168
|
+
out.push(JSON.parse(line));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
// Skip a torn or partial line (e.g. the process was killed mid-append).
|
|
171
|
+
// Hash-chain integrity is verified separately by verifyChain.
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
/**
|
|
@@ -26,6 +26,24 @@ function packageFolderName(name) {
|
|
|
26
26
|
return name.replace(/^@/, '').replace(/\//g, '-');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function assertSafePackageName(value) {
|
|
30
|
+
if (!/^@?[a-z0-9][a-z0-9-]*(\/[a-z0-9][a-z0-9-]*)?$/.test(value)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid extension name "${value}": expected a package name like ` +
|
|
33
|
+
`"@scope/pack" or "pack" using lowercase letters, digits, and dashes.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function assertSafeSegment(value, label) {
|
|
39
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Invalid ${label} "${value}": use lowercase letters, digits, and dashes ` +
|
|
42
|
+
`only. Slashes, dots, and ".." are rejected to prevent path traversal.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
function scaffold(outputRoot, opts = {}) {
|
|
30
48
|
const name = opts.name || '@godpowers/custom-pack';
|
|
31
49
|
const version = opts.version || '0.1.0';
|
|
@@ -34,6 +52,15 @@ function scaffold(outputRoot, opts = {}) {
|
|
|
34
52
|
const workflow = opts.workflow || null;
|
|
35
53
|
const range = opts.godpowersRange || '>=1.6.0';
|
|
36
54
|
const folder = opts.folder || packageFolderName(name);
|
|
55
|
+
|
|
56
|
+
// Validate all user-supplied names before they reach a filesystem path, so a
|
|
57
|
+
// value like "../../escape" can never write outside the output folder.
|
|
58
|
+
assertSafePackageName(name);
|
|
59
|
+
assertSafeSegment(skill, 'skill');
|
|
60
|
+
if (agent) assertSafeSegment(agent, 'agent');
|
|
61
|
+
if (workflow) assertSafeSegment(workflow, 'workflow');
|
|
62
|
+
assertSafeSegment(folder, 'folder');
|
|
63
|
+
|
|
37
64
|
const root = path.join(outputRoot, folder);
|
|
38
65
|
const written = [];
|
|
39
66
|
|
package/lib/feature-awareness.js
CHANGED
|
@@ -89,6 +89,24 @@ const FEATURES = [
|
|
|
89
89
|
commands: ['godpowers quick-proof'],
|
|
90
90
|
description: 'Render a shipped proof fixture with computed next action and host guarantees.'
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
id: 'request-trace-review',
|
|
94
|
+
since: '2.0.1',
|
|
95
|
+
commands: ['/god-build', '/god-review', '/god-feature', '/god-refactor'],
|
|
96
|
+
description: 'Keep implementation diffs narrow by requiring request-trace evidence and reviewer checks for scope, simplicity, and surgicality.'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'release-hardening',
|
|
100
|
+
since: '2.0.2',
|
|
101
|
+
commands: ['npm test', 'npm run lint', 'npm run release:check'],
|
|
102
|
+
description: 'Maintain release validation through a delegated test runner, static checks, parser coverage, and hardened package and runtime checks.'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'maintenance-hardening',
|
|
106
|
+
since: '2.0.3',
|
|
107
|
+
commands: ['npm test', 'npm run lint', 'npm run release:check'],
|
|
108
|
+
description: 'Track installer decomposition, shared test harness adoption, executable agent refs, skill metadata sync, God Mode runbook delegation, and async runtime APIs.'
|
|
109
|
+
},
|
|
92
110
|
{
|
|
93
111
|
id: 'extension-authoring',
|
|
94
112
|
since: '1.6.22',
|
package/lib/fs-async.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const fs = require('fs/promises');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
async function exists(filePath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(filePath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch (_) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function readJson(filePath) {
|
|
14
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function writeJson(filePath, value) {
|
|
18
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
19
|
+
await fs.writeFile(filePath, JSON.stringify(value, null, 2) + '\n');
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
exists,
|
|
25
|
+
readJson,
|
|
26
|
+
writeJson,
|
|
27
|
+
fs
|
|
28
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { RUNTIMES } = require('./installer-runtimes');
|
|
3
|
+
|
|
4
|
+
const COMMANDS = new Set([
|
|
5
|
+
'status',
|
|
6
|
+
'next',
|
|
7
|
+
'quick-proof',
|
|
8
|
+
'automation-status',
|
|
9
|
+
'automation-setup',
|
|
10
|
+
'dogfood',
|
|
11
|
+
'extension-scaffold'
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
function parseArgs(argv, cwd = process.cwd()) {
|
|
15
|
+
const args = argv.slice(2);
|
|
16
|
+
const opts = {
|
|
17
|
+
command: null,
|
|
18
|
+
project: cwd,
|
|
19
|
+
json: false,
|
|
20
|
+
brief: false,
|
|
21
|
+
extensionName: null,
|
|
22
|
+
extensionOutput: cwd,
|
|
23
|
+
extensionSkill: null,
|
|
24
|
+
extensionAgent: null,
|
|
25
|
+
extensionWorkflow: null,
|
|
26
|
+
runtimes: [],
|
|
27
|
+
global: false,
|
|
28
|
+
local: false,
|
|
29
|
+
all: false,
|
|
30
|
+
help: false,
|
|
31
|
+
uninstall: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < args.length; i++) {
|
|
35
|
+
const arg = args[i];
|
|
36
|
+
if (COMMANDS.has(arg)) {
|
|
37
|
+
opts.command = arg;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
switch (arg) {
|
|
42
|
+
case '--json':
|
|
43
|
+
opts.json = true;
|
|
44
|
+
break;
|
|
45
|
+
case '--brief':
|
|
46
|
+
opts.brief = true;
|
|
47
|
+
break;
|
|
48
|
+
case '--project':
|
|
49
|
+
if (args[i + 1]) {
|
|
50
|
+
opts.project = path.resolve(args[i + 1]);
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case '-g':
|
|
55
|
+
case '--global':
|
|
56
|
+
opts.global = true;
|
|
57
|
+
break;
|
|
58
|
+
case '-l':
|
|
59
|
+
case '--local':
|
|
60
|
+
opts.local = true;
|
|
61
|
+
break;
|
|
62
|
+
case '--all':
|
|
63
|
+
opts.all = true;
|
|
64
|
+
break;
|
|
65
|
+
case '-h':
|
|
66
|
+
case '--help':
|
|
67
|
+
opts.help = true;
|
|
68
|
+
break;
|
|
69
|
+
case '-u':
|
|
70
|
+
case '--uninstall':
|
|
71
|
+
opts.uninstall = true;
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
if (arg.startsWith('--project=')) {
|
|
75
|
+
opts.project = path.resolve(arg.slice('--project='.length));
|
|
76
|
+
} else if (arg.startsWith('--name=')) {
|
|
77
|
+
opts.extensionName = arg.slice('--name='.length);
|
|
78
|
+
} else if (arg.startsWith('--output=')) {
|
|
79
|
+
opts.extensionOutput = path.resolve(arg.slice('--output='.length));
|
|
80
|
+
} else if (arg.startsWith('--skill=')) {
|
|
81
|
+
opts.extensionSkill = arg.slice('--skill='.length);
|
|
82
|
+
} else if (arg.startsWith('--agent=')) {
|
|
83
|
+
opts.extensionAgent = arg.slice('--agent='.length);
|
|
84
|
+
} else if (arg.startsWith('--workflow=')) {
|
|
85
|
+
opts.extensionWorkflow = arg.slice('--workflow='.length);
|
|
86
|
+
} else if (arg.startsWith('--') && RUNTIMES[arg.slice(2)]) {
|
|
87
|
+
opts.runtimes.push(arg.slice(2));
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return opts;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
COMMANDS,
|
|
98
|
+
parseArgs
|
|
99
|
+
};
|