@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.
Files changed (100) hide show
  1. package/README.md +175 -0
  2. package/bin/forge.js +2 -0
  3. package/dist/addons/index.d.ts +25 -0
  4. package/dist/addons/index.js +139 -0
  5. package/dist/addons/index.js.map +1 -0
  6. package/dist/commands/add.d.ts +1 -0
  7. package/dist/commands/add.js +61 -0
  8. package/dist/commands/add.js.map +1 -0
  9. package/dist/commands/doctor.d.ts +1 -0
  10. package/dist/commands/doctor.js +177 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/ingest.d.ts +24 -0
  13. package/dist/commands/ingest.js +316 -0
  14. package/dist/commands/ingest.js.map +1 -0
  15. package/dist/commands/init.d.ts +8 -0
  16. package/dist/commands/init.js +557 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/remove.d.ts +1 -0
  19. package/dist/commands/remove.js +42 -0
  20. package/dist/commands/remove.js.map +1 -0
  21. package/dist/commands/status.d.ts +1 -0
  22. package/dist/commands/status.js +48 -0
  23. package/dist/commands/status.js.map +1 -0
  24. package/dist/commands/upgrade.d.ts +5 -0
  25. package/dist/commands/upgrade.js +190 -0
  26. package/dist/commands/upgrade.js.map +1 -0
  27. package/dist/detect/features.d.ts +10 -0
  28. package/dist/detect/features.js +33 -0
  29. package/dist/detect/features.js.map +1 -0
  30. package/dist/detect/go.d.ts +3 -0
  31. package/dist/detect/go.js +38 -0
  32. package/dist/detect/go.js.map +1 -0
  33. package/dist/detect/index.d.ts +25 -0
  34. package/dist/detect/index.js +32 -0
  35. package/dist/detect/index.js.map +1 -0
  36. package/dist/detect/node.d.ts +3 -0
  37. package/dist/detect/node.js +99 -0
  38. package/dist/detect/node.js.map +1 -0
  39. package/dist/detect/python.d.ts +3 -0
  40. package/dist/detect/python.js +86 -0
  41. package/dist/detect/python.js.map +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +51 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/render/engine.d.ts +8 -0
  46. package/dist/render/engine.js +71 -0
  47. package/dist/render/engine.js.map +1 -0
  48. package/dist/render/merge.d.ts +5 -0
  49. package/dist/render/merge.js +33 -0
  50. package/dist/render/merge.js.map +1 -0
  51. package/dist/utils/fs.d.ts +8 -0
  52. package/dist/utils/fs.js +42 -0
  53. package/dist/utils/fs.js.map +1 -0
  54. package/dist/utils/git.d.ts +3 -0
  55. package/dist/utils/git.js +31 -0
  56. package/dist/utils/git.js.map +1 -0
  57. package/dist/utils/hash.d.ts +8 -0
  58. package/dist/utils/hash.js +22 -0
  59. package/dist/utils/hash.js.map +1 -0
  60. package/dist/utils/yaml.d.ts +3 -0
  61. package/dist/utils/yaml.js +12 -0
  62. package/dist/utils/yaml.js.map +1 -0
  63. package/package.json +53 -0
  64. package/templates/addons/beads-dolt-backend/files/dolt-setup.sh +267 -0
  65. package/templates/addons/beads-dolt-backend/manifest.yaml +13 -0
  66. package/templates/addons/browser-testing/files/browser-smoke.sh +196 -0
  67. package/templates/addons/browser-testing/files/visual-qa.md +103 -0
  68. package/templates/addons/browser-testing/manifest.yaml +20 -0
  69. package/templates/addons/compliance-hipaa/files/hipaa-checks.sh +184 -0
  70. package/templates/addons/compliance-hipaa/files/hipaa-context.md +91 -0
  71. package/templates/addons/compliance-hipaa/manifest.yaml +15 -0
  72. package/templates/addons/compliance-soc2/files/soc2-checks.sh +232 -0
  73. package/templates/addons/compliance-soc2/files/soc2-context.md +147 -0
  74. package/templates/addons/compliance-soc2/manifest.yaml +15 -0
  75. package/templates/core/CLAUDE.md.hbs +70 -0
  76. package/templates/core/agents/architect.md.hbs +68 -0
  77. package/templates/core/agents/backend.md.hbs +27 -0
  78. package/templates/core/agents/frontend.md.hbs +25 -0
  79. package/templates/core/agents/quality.md.hbs +40 -0
  80. package/templates/core/agents/security.md.hbs +53 -0
  81. package/templates/core/context/project.md.hbs +60 -0
  82. package/templates/core/forge.yaml.hbs +69 -0
  83. package/templates/core/hooks/post-edit.sh.hbs +8 -0
  84. package/templates/core/hooks/pre-edit.sh.hbs +41 -0
  85. package/templates/core/hooks/session-start.sh.hbs +34 -0
  86. package/templates/core/pipeline/classify.sh.hbs +159 -0
  87. package/templates/core/pipeline/decompose.md.hbs +100 -0
  88. package/templates/core/pipeline/deliver.sh.hbs +171 -0
  89. package/templates/core/pipeline/execute.md.hbs +138 -0
  90. package/templates/core/pipeline/intake.sh.hbs +152 -0
  91. package/templates/core/pipeline/orchestrator.sh.hbs +361 -0
  92. package/templates/core/pipeline/verify.sh.hbs +160 -0
  93. package/templates/core/settings.json.hbs +55 -0
  94. package/templates/core/skill-creator.md.hbs +151 -0
  95. package/templates/core/skill-deliver.md.hbs +46 -0
  96. package/templates/core/skill-ingest.md.hbs +245 -0
  97. package/templates/presets/go/stack.md.hbs +133 -0
  98. package/templates/presets/python-fastapi/stack.md.hbs +101 -0
  99. package/templates/presets/react-next-ts/stack.md.hbs +77 -0
  100. 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,5 @@
1
+ /**
2
+ * Merge new forge.yaml fields into existing config without overwriting user values.
3
+ * Adds new keys with defaults, preserves existing values.
4
+ */
5
+ export declare function mergeForgeYaml(existing: string, updated: string): string;
@@ -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;
@@ -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,3 @@
1
+ export declare function isGitRepo(cwd: string): boolean;
2
+ export declare function getMainBranch(cwd: string): string;
3
+ export declare function getCurrentBranch(cwd: string): string;
@@ -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,3 @@
1
+ export declare function readYaml<T = unknown>(path: string): Promise<T>;
2
+ export declare function writeYaml(path: string, data: unknown): Promise<void>;
3
+ export { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
@@ -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
+ }