@samahlstrom/forge-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -0
- package/bin/forge.js +2 -0
- package/dist/addons/index.d.ts +25 -0
- package/dist/addons/index.js +139 -0
- package/dist/addons/index.js.map +1 -0
- package/dist/commands/add.d.ts +1 -0
- package/dist/commands/add.js +61 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +177 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/ingest.d.ts +24 -0
- package/dist/commands/ingest.js +316 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +557 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +42 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +48 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/upgrade.d.ts +5 -0
- package/dist/commands/upgrade.js +190 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/detect/features.d.ts +10 -0
- package/dist/detect/features.js +33 -0
- package/dist/detect/features.js.map +1 -0
- package/dist/detect/go.d.ts +3 -0
- package/dist/detect/go.js +38 -0
- package/dist/detect/go.js.map +1 -0
- package/dist/detect/index.d.ts +25 -0
- package/dist/detect/index.js +32 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/detect/node.d.ts +3 -0
- package/dist/detect/node.js +99 -0
- package/dist/detect/node.js.map +1 -0
- package/dist/detect/python.d.ts +3 -0
- package/dist/detect/python.js +86 -0
- package/dist/detect/python.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/render/engine.d.ts +8 -0
- package/dist/render/engine.js +71 -0
- package/dist/render/engine.js.map +1 -0
- package/dist/render/merge.d.ts +5 -0
- package/dist/render/merge.js +33 -0
- package/dist/render/merge.js.map +1 -0
- package/dist/utils/fs.d.ts +8 -0
- package/dist/utils/fs.js +42 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/git.d.ts +3 -0
- package/dist/utils/git.js +31 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/hash.d.ts +8 -0
- package/dist/utils/hash.js +22 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/yaml.d.ts +3 -0
- package/dist/utils/yaml.js +12 -0
- package/dist/utils/yaml.js.map +1 -0
- package/package.json +53 -0
- package/templates/addons/beads-dolt-backend/files/dolt-setup.sh +267 -0
- package/templates/addons/beads-dolt-backend/manifest.yaml +13 -0
- package/templates/addons/browser-testing/files/browser-smoke.sh +196 -0
- package/templates/addons/browser-testing/files/visual-qa.md +103 -0
- package/templates/addons/browser-testing/manifest.yaml +20 -0
- package/templates/addons/compliance-hipaa/files/hipaa-checks.sh +184 -0
- package/templates/addons/compliance-hipaa/files/hipaa-context.md +91 -0
- package/templates/addons/compliance-hipaa/manifest.yaml +15 -0
- package/templates/addons/compliance-soc2/files/soc2-checks.sh +232 -0
- package/templates/addons/compliance-soc2/files/soc2-context.md +147 -0
- package/templates/addons/compliance-soc2/manifest.yaml +15 -0
- package/templates/core/CLAUDE.md.hbs +70 -0
- package/templates/core/agents/architect.md.hbs +68 -0
- package/templates/core/agents/backend.md.hbs +27 -0
- package/templates/core/agents/frontend.md.hbs +25 -0
- package/templates/core/agents/quality.md.hbs +40 -0
- package/templates/core/agents/security.md.hbs +53 -0
- package/templates/core/context/project.md.hbs +60 -0
- package/templates/core/forge.yaml.hbs +69 -0
- package/templates/core/hooks/post-edit.sh.hbs +8 -0
- package/templates/core/hooks/pre-edit.sh.hbs +41 -0
- package/templates/core/hooks/session-start.sh.hbs +34 -0
- package/templates/core/pipeline/classify.sh.hbs +159 -0
- package/templates/core/pipeline/decompose.md.hbs +100 -0
- package/templates/core/pipeline/deliver.sh.hbs +171 -0
- package/templates/core/pipeline/execute.md.hbs +138 -0
- package/templates/core/pipeline/intake.sh.hbs +152 -0
- package/templates/core/pipeline/orchestrator.sh.hbs +361 -0
- package/templates/core/pipeline/verify.sh.hbs +160 -0
- package/templates/core/settings.json.hbs +55 -0
- package/templates/core/skill-creator.md.hbs +151 -0
- package/templates/core/skill-deliver.md.hbs +46 -0
- package/templates/core/skill-ingest.md.hbs +245 -0
- package/templates/presets/go/stack.md.hbs +133 -0
- package/templates/presets/python-fastapi/stack.md.hbs +101 -0
- package/templates/presets/react-next-ts/stack.md.hbs +77 -0
- package/templates/presets/sveltekit-ts/stack.md.hbs +116 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { init } from './commands/init.js';
|
|
3
|
+
import { ingest } from './commands/ingest.js';
|
|
4
|
+
import { add } from './commands/add.js';
|
|
5
|
+
import { remove } from './commands/remove.js';
|
|
6
|
+
import { upgrade } from './commands/upgrade.js';
|
|
7
|
+
import { status } from './commands/status.js';
|
|
8
|
+
import { doctor } from './commands/doctor.js';
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name('forge')
|
|
12
|
+
.description('Agent harness scaffolding for Claude Code')
|
|
13
|
+
.version('0.1.0');
|
|
14
|
+
program
|
|
15
|
+
.command('init')
|
|
16
|
+
.description('Initialize a new agent harness in the current project')
|
|
17
|
+
.option('--preset <preset>', 'Skip detection and use a specific preset')
|
|
18
|
+
.option('--force', 'Overwrite existing harness files')
|
|
19
|
+
.option('--yes', 'Accept all defaults without prompting')
|
|
20
|
+
.option('--spec <file>', 'Analyze a spec document to pre-fill project configuration')
|
|
21
|
+
.action(init);
|
|
22
|
+
program
|
|
23
|
+
.command('ingest')
|
|
24
|
+
.description('Ingest one or more spec documents for project planning and decomposition')
|
|
25
|
+
.argument('<files...>', 'One or more spec files (PDF, markdown, text)')
|
|
26
|
+
.option('--chunk-size <pages>', 'Pages per chunk for PDF processing', '20')
|
|
27
|
+
.option('--resume <spec-id>', 'Resume analysis of an existing spec')
|
|
28
|
+
.action(ingest);
|
|
29
|
+
program
|
|
30
|
+
.command('add <addon>')
|
|
31
|
+
.description('Install an optional addon (e.g., browser-testing, compliance-hipaa)')
|
|
32
|
+
.action(add);
|
|
33
|
+
program
|
|
34
|
+
.command('remove <addon>')
|
|
35
|
+
.description('Remove an installed addon')
|
|
36
|
+
.action(remove);
|
|
37
|
+
program
|
|
38
|
+
.command('upgrade')
|
|
39
|
+
.description('Upgrade core pipeline and addons to the latest version')
|
|
40
|
+
.option('--force', 'Overwrite all files without prompting')
|
|
41
|
+
.action(upgrade);
|
|
42
|
+
program
|
|
43
|
+
.command('status')
|
|
44
|
+
.description('Show installed preset, active addons, and pipeline health')
|
|
45
|
+
.action(status);
|
|
46
|
+
program
|
|
47
|
+
.command('doctor')
|
|
48
|
+
.description('Diagnose harness health: check files, scripts, deps, and config')
|
|
49
|
+
.action(doctor);
|
|
50
|
+
program.parse();
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACL,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEnB,OAAO;KACL,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,uDAAuD,CAAC;KACpE,MAAM,CAAC,mBAAmB,EAAE,0CAA0C,CAAC;KACvE,MAAM,CAAC,SAAS,EAAE,kCAAkC,CAAC;KACrD,MAAM,CAAC,OAAO,EAAE,uCAAuC,CAAC;KACxD,MAAM,CAAC,eAAe,EAAE,2DAA2D,CAAC;KACpF,MAAM,CAAC,IAAI,CAAC,CAAC;AAEf,OAAO;KACL,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,0EAA0E,CAAC;KACvF,QAAQ,CAAC,YAAY,EAAE,8CAA8C,CAAC;KACtE,MAAM,CAAC,sBAAsB,EAAE,oCAAoC,EAAE,IAAI,CAAC;KAC1E,MAAM,CAAC,oBAAoB,EAAE,qCAAqC,CAAC;KACnE,MAAM,CAAC,MAAM,CAAC,CAAC;AAEjB,OAAO;KACL,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,qEAAqE,CAAC;KAClF,MAAM,CAAC,GAAG,CAAC,CAAC;AAEd,OAAO;KACL,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,MAAM,CAAC,CAAC;AAEjB,OAAO;KACL,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,wDAAwD,CAAC;KACrE,MAAM,CAAC,SAAS,EAAE,uCAAuC,CAAC;KAC1D,MAAM,CAAC,OAAO,CAAC,CAAC;AAElB,OAAO;KACL,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,2DAA2D,CAAC;KACxE,MAAM,CAAC,MAAM,CAAC,CAAC;AAEjB,OAAO;KACL,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,iEAAiE,CAAC;KAC9E,MAAM,CAAC,MAAM,CAAC,CAAC;AAEjB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal template rendering engine.
|
|
3
|
+
* Supports: {{var}}, {{nested.var}}, {{#if cond}}...{{/if}}, {{#unless cond}}...{{/unless}}, {{#each items}}...{{/each}}
|
|
4
|
+
*/
|
|
5
|
+
export interface TemplateContext {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function render(template: string, ctx: TemplateContext): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal template rendering engine.
|
|
3
|
+
* Supports: {{var}}, {{nested.var}}, {{#if cond}}...{{/if}}, {{#unless cond}}...{{/unless}}, {{#each items}}...{{/each}}
|
|
4
|
+
*/
|
|
5
|
+
function resolve(path, ctx) {
|
|
6
|
+
return path.split('.').reduce((obj, key) => {
|
|
7
|
+
if (obj != null && typeof obj === 'object') {
|
|
8
|
+
return obj[key];
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}, ctx);
|
|
12
|
+
}
|
|
13
|
+
function isTruthy(val) {
|
|
14
|
+
if (Array.isArray(val))
|
|
15
|
+
return val.length > 0;
|
|
16
|
+
return Boolean(val);
|
|
17
|
+
}
|
|
18
|
+
export function render(template, ctx) {
|
|
19
|
+
let result = template;
|
|
20
|
+
// Process {{#each items}}...{{/each}} blocks
|
|
21
|
+
// Consume leading/trailing whitespace when tags are on their own line
|
|
22
|
+
result = result.replace(/[ \t]*\{\{#each\s+(\S+?)\}\}\n?([\s\S]*?)[ \t]*\{\{\/each\}\}\n?/g, (_match, key, body) => {
|
|
23
|
+
const items = resolve(key, ctx);
|
|
24
|
+
if (!Array.isArray(items))
|
|
25
|
+
return '';
|
|
26
|
+
return items
|
|
27
|
+
.map((item, index) => {
|
|
28
|
+
const itemCtx = {
|
|
29
|
+
...ctx,
|
|
30
|
+
'.': item,
|
|
31
|
+
'@index': index,
|
|
32
|
+
'@first': index === 0,
|
|
33
|
+
'@last': index === items.length - 1,
|
|
34
|
+
};
|
|
35
|
+
if (typeof item === 'object' && item !== null) {
|
|
36
|
+
Object.assign(itemCtx, item);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
itemCtx['this'] = item;
|
|
40
|
+
}
|
|
41
|
+
return render(body, itemCtx);
|
|
42
|
+
})
|
|
43
|
+
.join('');
|
|
44
|
+
});
|
|
45
|
+
// Process {{#if cond}}...{{else}}...{{/if}} blocks
|
|
46
|
+
result = result.replace(/[ \t]*\{\{#if\s+(\S+?)\}\}\n?([\s\S]*?)[ \t]*\{\{\/if\}\}\n?/g, (_match, key, body) => {
|
|
47
|
+
const parts = body.split(/[ \t]*\{\{else\}\}\n?/);
|
|
48
|
+
const val = resolve(key, ctx);
|
|
49
|
+
if (isTruthy(val)) {
|
|
50
|
+
return render(parts[0], ctx);
|
|
51
|
+
}
|
|
52
|
+
return parts[1] ? render(parts[1], ctx) : '';
|
|
53
|
+
});
|
|
54
|
+
// Process {{#unless cond}}...{{/unless}} blocks
|
|
55
|
+
result = result.replace(/[ \t]*\{\{#unless\s+(\S+?)\}\}\n?([\s\S]*?)[ \t]*\{\{\/unless\}\}\n?/g, (_match, key, body) => {
|
|
56
|
+
const val = resolve(key, ctx);
|
|
57
|
+
if (!isTruthy(val)) {
|
|
58
|
+
return render(body, ctx);
|
|
59
|
+
}
|
|
60
|
+
return '';
|
|
61
|
+
});
|
|
62
|
+
// Process {{variable}} substitutions
|
|
63
|
+
result = result.replace(/\{\{(\S+?)\}\}/g, (_match, key) => {
|
|
64
|
+
const val = resolve(key, ctx);
|
|
65
|
+
if (val === undefined || val === null)
|
|
66
|
+
return '';
|
|
67
|
+
return String(val);
|
|
68
|
+
});
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/render/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parse, stringify } from 'yaml';
|
|
2
|
+
/**
|
|
3
|
+
* Merge new forge.yaml fields into existing config without overwriting user values.
|
|
4
|
+
* Adds new keys with defaults, preserves existing values.
|
|
5
|
+
*/
|
|
6
|
+
export function mergeForgeYaml(existing, updated) {
|
|
7
|
+
const existingDoc = parse(existing);
|
|
8
|
+
const updatedDoc = parse(updated);
|
|
9
|
+
const merged = deepMergeNewOnly(existingDoc, updatedDoc);
|
|
10
|
+
return stringify(merged, { lineWidth: 120 });
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Deep merge that only adds keys from `source` that don't exist in `target`.
|
|
14
|
+
* Never overwrites existing values.
|
|
15
|
+
*/
|
|
16
|
+
function deepMergeNewOnly(target, source) {
|
|
17
|
+
const result = { ...target };
|
|
18
|
+
for (const key of Object.keys(source)) {
|
|
19
|
+
if (!(key in result)) {
|
|
20
|
+
result[key] = source[key];
|
|
21
|
+
}
|
|
22
|
+
else if (isPlainObject(result[key]) &&
|
|
23
|
+
isPlainObject(source[key])) {
|
|
24
|
+
result[key] = deepMergeNewOnly(result[key], source[key]);
|
|
25
|
+
}
|
|
26
|
+
// If key exists in target and isn't an object merge case, keep target's value
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function isPlainObject(val) {
|
|
31
|
+
return typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/render/merge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,OAAe;IAC/D,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAA4B,CAAC;IAC/D,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAA4B,CAAC;IAE7D,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACzD,OAAO,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CACxB,MAA+B,EAC/B,MAA+B;IAE/B,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAE7B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;aAAM,IACN,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EACzB,CAAC;YACF,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAC7B,MAAM,CAAC,GAAG,CAA4B,EACtC,MAAM,CAAC,GAAG,CAA4B,CACtC,CAAC;QACH,CAAC;QACD,8EAA8E;IAC/E,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,GAAY;IAClC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function exists(path: string): Promise<boolean>;
|
|
2
|
+
export declare function ensureDir(path: string): Promise<void>;
|
|
3
|
+
export declare function readText(path: string): Promise<string>;
|
|
4
|
+
export declare function writeText(path: string, content: string): Promise<void>;
|
|
5
|
+
export declare function readJson<T = unknown>(path: string): Promise<T>;
|
|
6
|
+
export declare function writeJson(path: string, data: unknown): Promise<void>;
|
|
7
|
+
export declare function listDir(path: string): Promise<string[]>;
|
|
8
|
+
export declare function resolveTemplatePath(...segments: string[]): string;
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
export async function exists(path) {
|
|
4
|
+
try {
|
|
5
|
+
await access(path);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function ensureDir(path) {
|
|
13
|
+
await mkdir(path, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
export async function readText(path) {
|
|
16
|
+
return readFile(path, 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
export async function writeText(path, content) {
|
|
19
|
+
await ensureDir(dirname(path));
|
|
20
|
+
await writeFile(path, content, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
export async function readJson(path) {
|
|
23
|
+
const text = await readText(path);
|
|
24
|
+
return JSON.parse(text);
|
|
25
|
+
}
|
|
26
|
+
export async function writeJson(path, data) {
|
|
27
|
+
await writeText(path, JSON.stringify(data, null, '\t') + '\n');
|
|
28
|
+
}
|
|
29
|
+
export async function listDir(path) {
|
|
30
|
+
try {
|
|
31
|
+
return await readdir(path);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function resolveTemplatePath(...segments) {
|
|
38
|
+
// Templates are relative to the package root, not the dist/ dir
|
|
39
|
+
const packageRoot = join(import.meta.dirname, '..', '..');
|
|
40
|
+
return join(packageRoot, 'templates', ...segments);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAY;IACxC,IAAI,CAAC;QACJ,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY;IAC3C,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IAC1C,OAAO,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,OAAe;IAC5D,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/B,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAc,IAAY;IACvD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,IAAa;IAC1D,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY;IACzC,IAAI,CAAC;QACJ,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAG,QAAkB;IACxD,gEAAgE;IAChE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,QAAQ,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
export function isGitRepo(cwd) {
|
|
3
|
+
try {
|
|
4
|
+
execSync('git rev-parse --is-inside-work-tree', { cwd, stdio: 'pipe' });
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function getMainBranch(cwd) {
|
|
12
|
+
try {
|
|
13
|
+
const result = execSync('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || echo refs/heads/main', { cwd, stdio: 'pipe' });
|
|
14
|
+
const ref = result.toString().trim();
|
|
15
|
+
return ref.replace('refs/remotes/origin/', '').replace('refs/heads/', '');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return 'main';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function getCurrentBranch(cwd) {
|
|
22
|
+
try {
|
|
23
|
+
return execSync('git branch --show-current', { cwd, stdio: 'pipe' })
|
|
24
|
+
.toString()
|
|
25
|
+
.trim();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,UAAU,SAAS,CAAC,GAAW;IACpC,IAAI,CAAC;QACJ,QAAQ,CAAC,qCAAqC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACxE,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACxC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,QAAQ,CACtB,+EAA+E,EAC/E,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CACtB,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,GAAG,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,MAAM,CAAC;IACf,CAAC;AACF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC3C,IAAI,CAAC;QACJ,OAAO,QAAQ,CAAC,2BAA2B,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;aAClE,QAAQ,EAAE;aACV,IAAI,EAAE,CAAC;IACV,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface HashManifest {
|
|
2
|
+
version: string;
|
|
3
|
+
files: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
export declare function hashContent(content: string): string;
|
|
6
|
+
export declare function hashFile(path: string): Promise<string>;
|
|
7
|
+
export declare function readHashes(projectRoot: string): Promise<HashManifest>;
|
|
8
|
+
export declare function writeHashes(projectRoot: string, manifest: HashManifest): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readText, writeJson, readJson, exists } from './fs.js';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export function hashContent(content) {
|
|
5
|
+
return 'sha256:' + createHash('sha256').update(content).digest('hex');
|
|
6
|
+
}
|
|
7
|
+
export async function hashFile(path) {
|
|
8
|
+
const content = await readText(path);
|
|
9
|
+
return hashContent(content);
|
|
10
|
+
}
|
|
11
|
+
const HASHES_FILE = '.forge/.hashes.json';
|
|
12
|
+
export async function readHashes(projectRoot) {
|
|
13
|
+
const path = join(projectRoot, HASHES_FILE);
|
|
14
|
+
if (await exists(path)) {
|
|
15
|
+
return readJson(path);
|
|
16
|
+
}
|
|
17
|
+
return { version: '0.0.0', files: {} };
|
|
18
|
+
}
|
|
19
|
+
export async function writeHashes(projectRoot, manifest) {
|
|
20
|
+
await writeJson(join(projectRoot, HASHES_FILE), manifest);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/utils/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC,MAAM,UAAU,WAAW,CAAC,OAAe;IAC1C,OAAO,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IAC1C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,WAAmB;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAC5C,IAAI,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,QAAQ,CAAe,IAAI,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,WAAmB,EACnB,QAAsB;IAEtB,MAAM,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { parse, stringify } from 'yaml';
|
|
2
|
+
import { readText, writeText } from './fs.js';
|
|
3
|
+
export async function readYaml(path) {
|
|
4
|
+
const text = await readText(path);
|
|
5
|
+
return parse(text);
|
|
6
|
+
}
|
|
7
|
+
export async function writeYaml(path, data) {
|
|
8
|
+
const text = stringify(data, { lineWidth: 120 });
|
|
9
|
+
await writeText(path, text);
|
|
10
|
+
}
|
|
11
|
+
export { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
12
|
+
//# sourceMappingURL=yaml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml.js","sourceRoot":"","sources":["../../src/utils/yaml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAc,IAAY;IACvD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,IAAI,CAAM,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,IAAa;IAC1D,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;IACjD,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@samahlstrom/forge-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent harness scaffolding for Claude Code — orchestration, decomposition, specialist agents, verification, and delivery.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"forge": "./bin/forge.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"lint": "eslint src/",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin/",
|
|
21
|
+
"dist/",
|
|
22
|
+
"templates/"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"claude-code",
|
|
26
|
+
"agent",
|
|
27
|
+
"harness",
|
|
28
|
+
"orchestration",
|
|
29
|
+
"ai",
|
|
30
|
+
"cli",
|
|
31
|
+
"scaffold"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@clack/prompts": "^0.9.1",
|
|
43
|
+
"chalk": "^5.4.1",
|
|
44
|
+
"commander": "^13.1.0",
|
|
45
|
+
"execa": "^9.5.2",
|
|
46
|
+
"yaml": "^2.7.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^22.15.0",
|
|
50
|
+
"typescript": "^5.8.3",
|
|
51
|
+
"vitest": "^3.1.1"
|
|
52
|
+
}
|
|
53
|
+
}
|