@ai-substrate/engineering-harness 0.2.0-canary.45
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/LICENSE +21 -0
- package/README.md +119 -0
- package/harness/cli/bin/harness.js +12 -0
- package/harness/cli/dist/acts/docs.d.ts +17 -0
- package/harness/cli/dist/acts/docs.js +73 -0
- package/harness/cli/dist/acts/docs.js.map +1 -0
- package/harness/cli/dist/acts/doctor.d.ts +14 -0
- package/harness/cli/dist/acts/doctor.js +43 -0
- package/harness/cli/dist/acts/doctor.js.map +1 -0
- package/harness/cli/dist/acts/help.d.ts +14 -0
- package/harness/cli/dist/acts/help.js +29 -0
- package/harness/cli/dist/acts/help.js.map +1 -0
- package/harness/cli/dist/acts/init.d.ts +22 -0
- package/harness/cli/dist/acts/init.js +61 -0
- package/harness/cli/dist/acts/init.js.map +1 -0
- package/harness/cli/dist/acts/instructions.d.ts +21 -0
- package/harness/cli/dist/acts/instructions.js +75 -0
- package/harness/cli/dist/acts/instructions.js.map +1 -0
- package/harness/cli/dist/acts/new.d.ts +19 -0
- package/harness/cli/dist/acts/new.js +66 -0
- package/harness/cli/dist/acts/new.js.map +1 -0
- package/harness/cli/dist/acts/observe.d.ts +23 -0
- package/harness/cli/dist/acts/observe.js +129 -0
- package/harness/cli/dist/acts/observe.js.map +1 -0
- package/harness/cli/dist/acts/record.d.ts +24 -0
- package/harness/cli/dist/acts/record.js +93 -0
- package/harness/cli/dist/acts/record.js.map +1 -0
- package/harness/cli/dist/acts/skills.d.ts +32 -0
- package/harness/cli/dist/acts/skills.js +256 -0
- package/harness/cli/dist/acts/skills.js.map +1 -0
- package/harness/cli/dist/acts/update.d.ts +23 -0
- package/harness/cli/dist/acts/update.js +297 -0
- package/harness/cli/dist/acts/update.js.map +1 -0
- package/harness/cli/dist/acts/verb.d.ts +27 -0
- package/harness/cli/dist/acts/verb.js +56 -0
- package/harness/cli/dist/acts/verb.js.map +1 -0
- package/harness/cli/dist/adapters/clock/clock-port.d.ts +10 -0
- package/harness/cli/dist/adapters/clock/clock-port.js +2 -0
- package/harness/cli/dist/adapters/clock/clock-port.js.map +1 -0
- package/harness/cli/dist/adapters/clock/fake-clock.d.ts +15 -0
- package/harness/cli/dist/adapters/clock/fake-clock.js +25 -0
- package/harness/cli/dist/adapters/clock/fake-clock.js.map +1 -0
- package/harness/cli/dist/adapters/clock/system-clock.d.ts +5 -0
- package/harness/cli/dist/adapters/clock/system-clock.js +7 -0
- package/harness/cli/dist/adapters/clock/system-clock.js.map +1 -0
- package/harness/cli/dist/adapters/env/env-port.d.ts +17 -0
- package/harness/cli/dist/adapters/env/env-port.js +2 -0
- package/harness/cli/dist/adapters/env/env-port.js.map +1 -0
- package/harness/cli/dist/adapters/env/fake-env.d.ts +15 -0
- package/harness/cli/dist/adapters/env/fake-env.js +24 -0
- package/harness/cli/dist/adapters/env/fake-env.js.map +1 -0
- package/harness/cli/dist/adapters/env/node-env.d.ts +6 -0
- package/harness/cli/dist/adapters/env/node-env.js +16 -0
- package/harness/cli/dist/adapters/env/node-env.js.map +1 -0
- package/harness/cli/dist/adapters/exec/exec-port.d.ts +22 -0
- package/harness/cli/dist/adapters/exec/exec-port.js +2 -0
- package/harness/cli/dist/adapters/exec/exec-port.js.map +1 -0
- package/harness/cli/dist/adapters/exec/fake-exec.d.ts +25 -0
- package/harness/cli/dist/adapters/exec/fake-exec.js +25 -0
- package/harness/cli/dist/adapters/exec/fake-exec.js.map +1 -0
- package/harness/cli/dist/adapters/exec/node-exec.d.ts +14 -0
- package/harness/cli/dist/adapters/exec/node-exec.js +38 -0
- package/harness/cli/dist/adapters/exec/node-exec.js.map +1 -0
- package/harness/cli/dist/adapters/fs/fake-fs.d.ts +22 -0
- package/harness/cli/dist/adapters/fs/fake-fs.js +63 -0
- package/harness/cli/dist/adapters/fs/fake-fs.js.map +1 -0
- package/harness/cli/dist/adapters/fs/fs-port.d.ts +20 -0
- package/harness/cli/dist/adapters/fs/fs-port.js +2 -0
- package/harness/cli/dist/adapters/fs/fs-port.js.map +1 -0
- package/harness/cli/dist/adapters/fs/node-fs.d.ts +9 -0
- package/harness/cli/dist/adapters/fs/node-fs.js +30 -0
- package/harness/cli/dist/adapters/fs/node-fs.js.map +1 -0
- package/harness/cli/dist/adapters/git/exec-git.d.ts +6 -0
- package/harness/cli/dist/adapters/git/exec-git.js +21 -0
- package/harness/cli/dist/adapters/git/exec-git.js.map +1 -0
- package/harness/cli/dist/adapters/git/fake-git.d.ts +15 -0
- package/harness/cli/dist/adapters/git/fake-git.js +20 -0
- package/harness/cli/dist/adapters/git/fake-git.js.map +1 -0
- package/harness/cli/dist/adapters/git/git-port.d.ts +12 -0
- package/harness/cli/dist/adapters/git/git-port.js +2 -0
- package/harness/cli/dist/adapters/git/git-port.js.map +1 -0
- package/harness/cli/dist/adapters/loader/fake-loader.d.ts +13 -0
- package/harness/cli/dist/adapters/loader/fake-loader.js +25 -0
- package/harness/cli/dist/adapters/loader/fake-loader.js.map +1 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.d.ts +15 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.js +29 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.js.map +1 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.d.ts +12 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.js +2 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.js.map +1 -0
- package/harness/cli/dist/adapters/process/fake-process.d.ts +13 -0
- package/harness/cli/dist/adapters/process/fake-process.js +21 -0
- package/harness/cli/dist/adapters/process/fake-process.js.map +1 -0
- package/harness/cli/dist/adapters/process/node-process.d.ts +6 -0
- package/harness/cli/dist/adapters/process/node-process.js +17 -0
- package/harness/cli/dist/adapters/process/node-process.js.map +1 -0
- package/harness/cli/dist/adapters/process/process-port.d.ts +13 -0
- package/harness/cli/dist/adapters/process/process-port.js +2 -0
- package/harness/cli/dist/adapters/process/process-port.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.d.ts +13 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js +21 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.d.ts +18 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js +51 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.d.ts +19 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js +2 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js.map +1 -0
- package/harness/cli/dist/app.d.ts +70 -0
- package/harness/cli/dist/app.js +221 -0
- package/harness/cli/dist/app.js.map +1 -0
- package/harness/cli/dist/index.d.ts +2 -0
- package/harness/cli/dist/index.js +26 -0
- package/harness/cli/dist/index.js.map +1 -0
- package/harness/cli/dist/output/envelope.d.ts +68 -0
- package/harness/cli/dist/output/envelope.js +56 -0
- package/harness/cli/dist/output/envelope.js.map +1 -0
- package/harness/cli/dist/output/error-codes.d.ts +57 -0
- package/harness/cli/dist/output/error-codes.js +57 -0
- package/harness/cli/dist/output/error-codes.js.map +1 -0
- package/harness/cli/dist/output/exit.d.ts +29 -0
- package/harness/cli/dist/output/exit.js +36 -0
- package/harness/cli/dist/output/exit.js.map +1 -0
- package/harness/cli/dist/output/output-port.d.ts +54 -0
- package/harness/cli/dist/output/output-port.js +55 -0
- package/harness/cli/dist/output/output-port.js.map +1 -0
- package/harness/cli/dist/output/style.d.ts +33 -0
- package/harness/cli/dist/output/style.js +68 -0
- package/harness/cli/dist/output/style.js.map +1 -0
- package/harness/cli/dist/services/config/load-config.d.ts +27 -0
- package/harness/cli/dist/services/config/load-config.js +114 -0
- package/harness/cli/dist/services/config/load-config.js.map +1 -0
- package/harness/cli/dist/services/docs/contract.d.ts +41 -0
- package/harness/cli/dist/services/docs/contract.js +14 -0
- package/harness/cli/dist/services/docs/contract.js.map +1 -0
- package/harness/cli/dist/services/docs/docs-content.d.ts +37 -0
- package/harness/cli/dist/services/docs/docs-content.js +48 -0
- package/harness/cli/dist/services/docs/docs-content.js.map +1 -0
- package/harness/cli/dist/services/docs/docs-service.d.ts +26 -0
- package/harness/cli/dist/services/docs/docs-service.js +25 -0
- package/harness/cli/dist/services/docs/docs-service.js.map +1 -0
- package/harness/cli/dist/services/doctor/doctor-service.d.ts +69 -0
- package/harness/cli/dist/services/doctor/doctor-service.js +237 -0
- package/harness/cli/dist/services/doctor/doctor-service.js.map +1 -0
- package/harness/cli/dist/services/extensions/contract.d.ts +138 -0
- package/harness/cli/dist/services/extensions/contract.js +17 -0
- package/harness/cli/dist/services/extensions/contract.js.map +1 -0
- package/harness/cli/dist/services/extensions/discovery.d.ts +53 -0
- package/harness/cli/dist/services/extensions/discovery.js +116 -0
- package/harness/cli/dist/services/extensions/discovery.js.map +1 -0
- package/harness/cli/dist/services/extensions/registry.d.ts +63 -0
- package/harness/cli/dist/services/extensions/registry.js +165 -0
- package/harness/cli/dist/services/extensions/registry.js.map +1 -0
- package/harness/cli/dist/services/extensions/verb-context.d.ts +44 -0
- package/harness/cli/dist/services/extensions/verb-context.js +97 -0
- package/harness/cli/dist/services/extensions/verb-context.js.map +1 -0
- package/harness/cli/dist/services/help/help-service.d.ts +42 -0
- package/harness/cli/dist/services/help/help-service.js +108 -0
- package/harness/cli/dist/services/help/help-service.js.map +1 -0
- package/harness/cli/dist/services/init/governance-template.d.ts +27 -0
- package/harness/cli/dist/services/init/governance-template.js +72 -0
- package/harness/cli/dist/services/init/governance-template.js.map +1 -0
- package/harness/cli/dist/services/init/init-service.d.ts +38 -0
- package/harness/cli/dist/services/init/init-service.js +44 -0
- package/harness/cli/dist/services/init/init-service.js.map +1 -0
- package/harness/cli/dist/services/instructions/core-instructions.d.ts +11 -0
- package/harness/cli/dist/services/instructions/core-instructions.js +80 -0
- package/harness/cli/dist/services/instructions/core-instructions.js.map +1 -0
- package/harness/cli/dist/services/instructions/instructions-service.d.ts +52 -0
- package/harness/cli/dist/services/instructions/instructions-service.js +53 -0
- package/harness/cli/dist/services/instructions/instructions-service.js.map +1 -0
- package/harness/cli/dist/services/observe/buffer-codec.d.ts +51 -0
- package/harness/cli/dist/services/observe/buffer-codec.js +139 -0
- package/harness/cli/dist/services/observe/buffer-codec.js.map +1 -0
- package/harness/cli/dist/services/observe/observe-service.d.ts +87 -0
- package/harness/cli/dist/services/observe/observe-service.js +221 -0
- package/harness/cli/dist/services/observe/observe-service.js.map +1 -0
- package/harness/cli/dist/services/record/contract.d.ts +32 -0
- package/harness/cli/dist/services/record/contract.js +17 -0
- package/harness/cli/dist/services/record/contract.js.map +1 -0
- package/harness/cli/dist/services/record/core-types/retro.d.ts +20 -0
- package/harness/cli/dist/services/record/core-types/retro.js +55 -0
- package/harness/cli/dist/services/record/core-types/retro.js.map +1 -0
- package/harness/cli/dist/services/record/record-service.d.ts +38 -0
- package/harness/cli/dist/services/record/record-service.js +144 -0
- package/harness/cli/dist/services/record/record-service.js.map +1 -0
- package/harness/cli/dist/services/record/registry.d.ts +46 -0
- package/harness/cli/dist/services/record/registry.js +71 -0
- package/harness/cli/dist/services/record/registry.js.map +1 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.d.ts +29 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.js +88 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.js.map +1 -0
- package/harness/cli/dist/services/scaffold/templates.d.ts +42 -0
- package/harness/cli/dist/services/scaffold/templates.js +178 -0
- package/harness/cli/dist/services/scaffold/templates.js.map +1 -0
- package/harness/cli/dist/services/shared/posix-path.d.ts +54 -0
- package/harness/cli/dist/services/shared/posix-path.js +94 -0
- package/harness/cli/dist/services/shared/posix-path.js.map +1 -0
- package/harness/cli/dist/services/shared/temp.d.ts +24 -0
- package/harness/cli/dist/services/shared/temp.js +29 -0
- package/harness/cli/dist/services/shared/temp.js.map +1 -0
- package/harness/cli/dist/services/skills/contract.d.ts +52 -0
- package/harness/cli/dist/services/skills/contract.js +55 -0
- package/harness/cli/dist/services/skills/contract.js.map +1 -0
- package/harness/cli/dist/services/skills/skills-service.d.ts +73 -0
- package/harness/cli/dist/services/skills/skills-service.js +132 -0
- package/harness/cli/dist/services/skills/skills-service.js.map +1 -0
- package/harness/cli/dist/services/update/banner.d.ts +26 -0
- package/harness/cli/dist/services/update/banner.js +28 -0
- package/harness/cli/dist/services/update/banner.js.map +1 -0
- package/harness/cli/dist/services/update/cache.d.ts +21 -0
- package/harness/cli/dist/services/update/cache.js +61 -0
- package/harness/cli/dist/services/update/cache.js.map +1 -0
- package/harness/cli/dist/services/update/constants.d.ts +9 -0
- package/harness/cli/dist/services/update/constants.js +10 -0
- package/harness/cli/dist/services/update/constants.js.map +1 -0
- package/harness/cli/dist/services/update/install.d.ts +26 -0
- package/harness/cli/dist/services/update/install.js +78 -0
- package/harness/cli/dist/services/update/install.js.map +1 -0
- package/harness/cli/dist/services/update/semver.d.ts +16 -0
- package/harness/cli/dist/services/update/semver.js +108 -0
- package/harness/cli/dist/services/update/semver.js.map +1 -0
- package/harness/cli/dist/services/update/update-service.d.ts +46 -0
- package/harness/cli/dist/services/update/update-service.js +91 -0
- package/harness/cli/dist/services/update/update-service.js.map +1 -0
- package/harness/cli/dist/version.d.ts +8 -0
- package/harness/cli/dist/version.js +15 -0
- package/harness/cli/dist/version.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { exitWithEnvelope } from '../output/exit.js';
|
|
2
|
+
import { createOutputPort } from '../output/output-port.js';
|
|
3
|
+
import { buildVerbContext, runVerb } from '../services/extensions/verb-context.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register ONE extension verb as a top-level `harness <verb>` subcommand. Thin:
|
|
6
|
+
* it maps the declarative `HarnessVerb` onto commander (name, description,
|
|
7
|
+
* options, args), and its action parses opts/args → builds the `VerbContext`
|
|
8
|
+
* (cwd from the process port) → `await runVerb` → exits via the kernel (so the
|
|
9
|
+
* status→exit mapping + `process.exit` confinement are unchanged). No business
|
|
10
|
+
* logic lives here. Returns the created Command (usage is inspectable).
|
|
11
|
+
*/
|
|
12
|
+
export function registerVerbAct(program, verb, deps, io) {
|
|
13
|
+
const command = program.command(verb.name);
|
|
14
|
+
command.description(verb.description ?? verb.summary);
|
|
15
|
+
command.summary(verb.summary);
|
|
16
|
+
// Group every contributed verb under its own `--help` heading so the dynamic,
|
|
17
|
+
// extension-owned surface reads separately from the fixed core commands.
|
|
18
|
+
command.helpGroup('Extensions:');
|
|
19
|
+
const args = verb.args ?? [];
|
|
20
|
+
for (const arg of args) {
|
|
21
|
+
command.argument(arg.name, arg.description);
|
|
22
|
+
}
|
|
23
|
+
for (const option of verb.options ?? []) {
|
|
24
|
+
if (option.defaultValue !== undefined) {
|
|
25
|
+
command.option(option.flags, option.description, option.defaultValue);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
command.option(option.flags, option.description);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
command.action(async (...callArgs) => {
|
|
32
|
+
const cmd = callArgs[callArgs.length - 1];
|
|
33
|
+
const positionals = callArgs.slice(0, args.length);
|
|
34
|
+
const ctx = buildVerbContext(deps, {
|
|
35
|
+
cwd: deps.proc.cwd(),
|
|
36
|
+
args: buildArgMap(args, positionals),
|
|
37
|
+
options: cmd.opts(),
|
|
38
|
+
});
|
|
39
|
+
const envelope = await runVerb(verb, ctx, deps.clock);
|
|
40
|
+
exitWithEnvelope(envelope, createOutputPort(io.mode, io.writers));
|
|
41
|
+
});
|
|
42
|
+
return command;
|
|
43
|
+
}
|
|
44
|
+
/** Map declared args (`<name>`, `[env]`, `<files...>`) to a `{argKey: value}` record. */
|
|
45
|
+
function buildArgMap(args, positionals) {
|
|
46
|
+
const result = {};
|
|
47
|
+
args.forEach((arg, index) => {
|
|
48
|
+
result[argKey(arg.name)] = positionals[index];
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/** Strip commander placeholder punctuation: `<files...>` → `files`. */
|
|
53
|
+
function argKey(name) {
|
|
54
|
+
return name.replace(/[<>[\].]/g, '').trim();
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=verb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verb.js","sourceRoot":"","sources":["../../src/acts/verb.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAc,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,wCAAwC,CAAC;AAYnF;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAgB,EAChB,IAAiB,EACjB,IAAiB,EACjB,EAAS;IAET,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9B,8EAA8E;IAC9E,yEAAyE;IACzE,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACxC,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACtC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,QAAmB,EAAE,EAAE;QAC9C,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAY,CAAC;QACrD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAA2B,CAAC;QAC7E,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,EAAE;YACjC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACpB,IAAI,EAAE,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC;YACpC,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;SACpB,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,yFAAyF;AACzF,SAAS,WAAW,CAClB,IAAwB,EACxB,WAAmC;IAEnC,MAAM,MAAM,GAAuC,EAAE,CAAC;IACtD,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;QAC1B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,uEAAuE;AACvE,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clock port — the kernel's only side-effect dependency.
|
|
3
|
+
*
|
|
4
|
+
* Injected (never `new Date()` inside services/kernel) so envelope timestamps
|
|
5
|
+
* are deterministic in unit tests. See workshop 001 + plan Finding 04.
|
|
6
|
+
*/
|
|
7
|
+
export interface Clock {
|
|
8
|
+
/** Current instant as an ISO-8601 string, e.g. "2026-06-08T07:20:00.000Z". */
|
|
9
|
+
nowIso(): string;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock-port.js","sourceRoot":"","sources":["../../../src/adapters/clock/clock-port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Clock } from './clock-port.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic Clock for tests. Returns a fixed instant until advanced/set,
|
|
4
|
+
* and records its call history (fakes over mocks — assert on `calls`).
|
|
5
|
+
*/
|
|
6
|
+
export declare class FakeClock implements Clock {
|
|
7
|
+
private current;
|
|
8
|
+
readonly calls: string[];
|
|
9
|
+
constructor(start?: string | number | Date);
|
|
10
|
+
nowIso(): string;
|
|
11
|
+
/** Advance the fake clock forward by `ms` milliseconds. */
|
|
12
|
+
advance(ms: number): void;
|
|
13
|
+
/** Jump the fake clock to an absolute instant. */
|
|
14
|
+
set(instant: string | number | Date): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Clock for tests. Returns a fixed instant until advanced/set,
|
|
3
|
+
* and records its call history (fakes over mocks — assert on `calls`).
|
|
4
|
+
*/
|
|
5
|
+
export class FakeClock {
|
|
6
|
+
current;
|
|
7
|
+
calls = [];
|
|
8
|
+
constructor(start = '2026-06-08T07:20:00.000Z') {
|
|
9
|
+
this.current = new Date(start).getTime();
|
|
10
|
+
}
|
|
11
|
+
nowIso() {
|
|
12
|
+
const iso = new Date(this.current).toISOString();
|
|
13
|
+
this.calls.push(iso);
|
|
14
|
+
return iso;
|
|
15
|
+
}
|
|
16
|
+
/** Advance the fake clock forward by `ms` milliseconds. */
|
|
17
|
+
advance(ms) {
|
|
18
|
+
this.current += ms;
|
|
19
|
+
}
|
|
20
|
+
/** Jump the fake clock to an absolute instant. */
|
|
21
|
+
set(instant) {
|
|
22
|
+
this.current = new Date(instant).getTime();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=fake-clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-clock.js","sourceRoot":"","sources":["../../../src/adapters/clock/fake-clock.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,SAAS;IACZ,OAAO,CAAS;IACf,KAAK,GAAa,EAAE,CAAC;IAE9B,YAAY,QAAgC,0BAA0B;QACpE,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QACjD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,2DAA2D;IAC3D,OAAO,CAAC,EAAU;QAChB,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;IACrB,CAAC;IAED,kDAAkD;IAClD,GAAG,CAAC,OAA+B;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;IAC7C,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-clock.js","sourceRoot":"","sources":["../../../src/adapters/clock/system-clock.ts"],"names":[],"mappings":"AAEA,2DAA2D;AAC3D,MAAM,OAAO,WAAW;IACtB,MAAM;QACJ,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClC,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment port — reads process environment variables behind an interface.
|
|
3
|
+
*
|
|
4
|
+
* Lets services read/report env (e.g. `HARNESS_JSON`) without touching
|
|
5
|
+
* `process.env` directly, so they stay unit-testable with `FakeEnv`.
|
|
6
|
+
*/
|
|
7
|
+
export interface EnvPort {
|
|
8
|
+
/** Value of an env var, or undefined if unset. */
|
|
9
|
+
get(name: string): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Absolute path to the user's home directory (`$HOME` / `%USERPROFILE%`), or
|
|
12
|
+
* undefined if it cannot be resolved. Lets services place user-global state
|
|
13
|
+
* (e.g. the update-check cache under `~/.harness/`) without reading
|
|
14
|
+
* `os.homedir()` directly — keeping them free of `node:*` (P2).
|
|
15
|
+
*/
|
|
16
|
+
home(): string | undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-port.js","sourceRoot":"","sources":["../../../src/adapters/env/env-port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EnvPort } from './env-port.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic environment for tests. Seeded with a `{name: value}` map;
|
|
4
|
+
* records every requested name on `gets` (fakes over mocks).
|
|
5
|
+
*/
|
|
6
|
+
export declare class FakeEnv implements EnvPort {
|
|
7
|
+
private readonly vars;
|
|
8
|
+
private readonly homeDir?;
|
|
9
|
+
readonly gets: string[];
|
|
10
|
+
/** How many times home() was called (fakes over mocks — assert on history). */
|
|
11
|
+
homeCalls: number;
|
|
12
|
+
constructor(vars?: Record<string, string>, homeDir?: string | undefined);
|
|
13
|
+
get(name: string): string | undefined;
|
|
14
|
+
home(): string | undefined;
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic environment for tests. Seeded with a `{name: value}` map;
|
|
3
|
+
* records every requested name on `gets` (fakes over mocks).
|
|
4
|
+
*/
|
|
5
|
+
export class FakeEnv {
|
|
6
|
+
vars;
|
|
7
|
+
homeDir;
|
|
8
|
+
gets = [];
|
|
9
|
+
/** How many times home() was called (fakes over mocks — assert on history). */
|
|
10
|
+
homeCalls = 0;
|
|
11
|
+
constructor(vars = {}, homeDir) {
|
|
12
|
+
this.vars = vars;
|
|
13
|
+
this.homeDir = homeDir;
|
|
14
|
+
}
|
|
15
|
+
get(name) {
|
|
16
|
+
this.gets.push(name);
|
|
17
|
+
return this.vars[name];
|
|
18
|
+
}
|
|
19
|
+
home() {
|
|
20
|
+
this.homeCalls++;
|
|
21
|
+
return this.homeDir;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=fake-env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-env.js","sourceRoot":"","sources":["../../../src/adapters/env/fake-env.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,OAAO;IAMC;IACA;IANV,IAAI,GAAa,EAAE,CAAC;IAC7B,+EAA+E;IAC/E,SAAS,GAAG,CAAC,CAAC;IAEd,YACmB,OAA+B,EAAE,EACjC,OAAgB;QADhB,SAAI,GAAJ,IAAI,CAA6B;QACjC,YAAO,GAAP,OAAO,CAAS;IAChC,CAAC;IAEJ,GAAG,CAAC,IAAY;QACd,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
/** Real environment — wraps `process.env`. */
|
|
3
|
+
export class NodeEnv {
|
|
4
|
+
get(name) {
|
|
5
|
+
return process.env[name];
|
|
6
|
+
}
|
|
7
|
+
home() {
|
|
8
|
+
// Prefer the explicit env vars ($HOME on POSIX, %USERPROFILE% on Windows),
|
|
9
|
+
// then fall back to os.homedir(). `||` (not `??`) so an EMPTY string falls
|
|
10
|
+
// through to the next source rather than being treated as a resolved value
|
|
11
|
+
// (companion F001). Empty after all sources ⇒ unresolved ⇒ undefined.
|
|
12
|
+
const resolved = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
13
|
+
return resolved ? resolved : undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=node-env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-env.js","sourceRoot":"","sources":["../../../src/adapters/env/node-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,8CAA8C;AAC9C,MAAM,OAAO,OAAO;IAClB,GAAG,CAAC,IAAY;QACd,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI;QACF,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,EAAE,CAAC;QAC1E,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACzC,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exec port — runs a REAL repo command (the P8 "wrap, don't rebuild" capability).
|
|
3
|
+
*
|
|
4
|
+
* Verbs invoke this through `ctx.exec` to wrap existing project commands (build,
|
|
5
|
+
* lint, test…). Injected so verb/loader logic stays unit-testable with `FakeExec`
|
|
6
|
+
* and never spawns a child directly — `NodeExec` is the only place a child is
|
|
7
|
+
* spawned for the verb path (KF-06 adapter discipline).
|
|
8
|
+
*/
|
|
9
|
+
export interface ExecResult {
|
|
10
|
+
/** Child process exit code (127 when the binary could not be spawned). */
|
|
11
|
+
code: number;
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
/** Convenience: `code === 0`. */
|
|
15
|
+
ok: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ExecPort {
|
|
18
|
+
/** Spawn `command args` in `opts.cwd` (no shell), capturing code/stdout/stderr. */
|
|
19
|
+
run(command: string, args: string[], opts: {
|
|
20
|
+
cwd: string;
|
|
21
|
+
}): Promise<ExecResult>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec-port.js","sourceRoot":"","sources":["../../../src/adapters/exec/exec-port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ExecPort, ExecResult } from './exec-port.js';
|
|
2
|
+
/** A scripted result — `ok` is derived from `code`, so callers script only the facts. */
|
|
3
|
+
export interface ExecScript {
|
|
4
|
+
code: number;
|
|
5
|
+
stdout?: string;
|
|
6
|
+
stderr?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Deterministic exec for tests. Seeded with `{ 'cmd a b': {code,stdout?,stderr?} }`
|
|
10
|
+
* keyed by full command line; records each `{command,args,cwd}` on `calls` (fakes
|
|
11
|
+
* over mocks). An unscripted command resolves to a benign success (absent ≠ error,
|
|
12
|
+
* mirroring FakeFs/FakeProcess); `ok` is always computed from `code`.
|
|
13
|
+
*/
|
|
14
|
+
export declare class FakeExec implements ExecPort {
|
|
15
|
+
private readonly scripts;
|
|
16
|
+
readonly calls: {
|
|
17
|
+
command: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
cwd: string;
|
|
20
|
+
}[];
|
|
21
|
+
constructor(scripts?: Record<string, ExecScript>);
|
|
22
|
+
run(command: string, args: string[], opts: {
|
|
23
|
+
cwd: string;
|
|
24
|
+
}): Promise<ExecResult>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic exec for tests. Seeded with `{ 'cmd a b': {code,stdout?,stderr?} }`
|
|
3
|
+
* keyed by full command line; records each `{command,args,cwd}` on `calls` (fakes
|
|
4
|
+
* over mocks). An unscripted command resolves to a benign success (absent ≠ error,
|
|
5
|
+
* mirroring FakeFs/FakeProcess); `ok` is always computed from `code`.
|
|
6
|
+
*/
|
|
7
|
+
export class FakeExec {
|
|
8
|
+
scripts;
|
|
9
|
+
calls = [];
|
|
10
|
+
constructor(scripts = {}) {
|
|
11
|
+
this.scripts = scripts;
|
|
12
|
+
}
|
|
13
|
+
run(command, args, opts) {
|
|
14
|
+
this.calls.push({ command, args, cwd: opts.cwd });
|
|
15
|
+
const key = [command, ...args].join(' ');
|
|
16
|
+
const script = this.scripts[key] ?? this.scripts[command] ?? { code: 0 };
|
|
17
|
+
return Promise.resolve({
|
|
18
|
+
code: script.code,
|
|
19
|
+
stdout: script.stdout ?? '',
|
|
20
|
+
stderr: script.stderr ?? '',
|
|
21
|
+
ok: script.code === 0,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=fake-exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-exec.js","sourceRoot":"","sources":["../../../src/adapters/exec/fake-exec.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,MAAM,OAAO,QAAQ;IAGU;IAFpB,KAAK,GAAuD,EAAE,CAAC;IAExE,YAA6B,UAAsC,EAAE;QAAxC,YAAO,GAAP,OAAO,CAAiC;IAAG,CAAC;IAEzE,GAAG,CAAC,OAAe,EAAE,IAAc,EAAE,IAAqB;QACxD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACzE,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,EAAE,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC;SACtB,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ExecPort, ExecResult } from './exec-port.js';
|
|
2
|
+
/**
|
|
3
|
+
* Real process execution — the only place a child is spawned for the verb path.
|
|
4
|
+
* `shell: false` + an args array means no shell-injection surface (KF-06). Never
|
|
5
|
+
* rejects: a spawn error or non-zero exit resolves to an `ExecResult` so the verb
|
|
6
|
+
* handler can map it to an Envelope rather than throwing through the kernel. This
|
|
7
|
+
* includes a SYNCHRONOUS spawn throw (e.g. a null byte in the command), which is
|
|
8
|
+
* caught and resolved as code 127 rather than rejecting the promise.
|
|
9
|
+
*/
|
|
10
|
+
export declare class NodeExec implements ExecPort {
|
|
11
|
+
run(command: string, args: string[], opts: {
|
|
12
|
+
cwd: string;
|
|
13
|
+
}): Promise<ExecResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Real process execution — the only place a child is spawned for the verb path.
|
|
4
|
+
* `shell: false` + an args array means no shell-injection surface (KF-06). Never
|
|
5
|
+
* rejects: a spawn error or non-zero exit resolves to an `ExecResult` so the verb
|
|
6
|
+
* handler can map it to an Envelope rather than throwing through the kernel. This
|
|
7
|
+
* includes a SYNCHRONOUS spawn throw (e.g. a null byte in the command), which is
|
|
8
|
+
* caught and resolved as code 127 rather than rejecting the promise.
|
|
9
|
+
*/
|
|
10
|
+
export class NodeExec {
|
|
11
|
+
run(command, args, opts) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
let stdout = '';
|
|
14
|
+
let stderr = '';
|
|
15
|
+
try {
|
|
16
|
+
const child = spawn(command, args, { cwd: opts.cwd, shell: false });
|
|
17
|
+
child.stdout?.on('data', (chunk) => {
|
|
18
|
+
stdout += chunk.toString();
|
|
19
|
+
});
|
|
20
|
+
child.stderr?.on('data', (chunk) => {
|
|
21
|
+
stderr += chunk.toString();
|
|
22
|
+
});
|
|
23
|
+
child.on('error', (err) => {
|
|
24
|
+
resolve({ code: 127, stdout, stderr: stderr + String(err.message ?? err), ok: false });
|
|
25
|
+
});
|
|
26
|
+
child.on('close', (code) => {
|
|
27
|
+
const exitCode = code ?? 1;
|
|
28
|
+
resolve({ code: exitCode, stdout, stderr, ok: exitCode === 0 });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
resolve({ code: 127, stdout, stderr: stderr + message, ok: false });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=node-exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-exec.js","sourceRoot":"","sources":["../../../src/adapters/exec/node-exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C;;;;;;;GAOG;AACH,MAAM,OAAO,QAAQ;IACnB,GAAG,CAAC,OAAe,EAAE,IAAc,EAAE,IAAqB;QACxD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;gBACpE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;oBACjC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC7B,CAAC,CAAC,CAAC;gBACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;oBACjC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC7B,CAAC,CAAC,CAAC;gBACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBACxB,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBACzF,CAAC,CAAC,CAAC;gBACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;oBACzB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC;oBAC3B,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC,CAAC;gBAClE,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FsPort } from './fs-port.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic filesystem for tests. Seeded with a `{path: contents}` map and
|
|
4
|
+
* an optional `{dir: entryNames[]}` map; records every probed path on `reads`,
|
|
5
|
+
* every written path on `writes`, and every `mkdirp` on `mkdirs` (fakes over
|
|
6
|
+
* mocks — assert on history). Writes mutate the in-memory file map so a later
|
|
7
|
+
* `exists`/`readText` sees what was written.
|
|
8
|
+
*/
|
|
9
|
+
export declare class FakeFs implements FsPort {
|
|
10
|
+
private readonly files;
|
|
11
|
+
private readonly dirs;
|
|
12
|
+
readonly reads: string[];
|
|
13
|
+
readonly writes: string[];
|
|
14
|
+
readonly mkdirs: string[];
|
|
15
|
+
private readonly madeDirs;
|
|
16
|
+
constructor(files?: Record<string, string>, dirs?: Record<string, string[]>);
|
|
17
|
+
exists(path: string): boolean;
|
|
18
|
+
readText(path: string): string | null;
|
|
19
|
+
readdir(path: string): string[];
|
|
20
|
+
mkdirp(path: string): void;
|
|
21
|
+
writeText(path: string, contents: string): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic filesystem for tests. Seeded with a `{path: contents}` map and
|
|
3
|
+
* an optional `{dir: entryNames[]}` map; records every probed path on `reads`,
|
|
4
|
+
* every written path on `writes`, and every `mkdirp` on `mkdirs` (fakes over
|
|
5
|
+
* mocks — assert on history). Writes mutate the in-memory file map so a later
|
|
6
|
+
* `exists`/`readText` sees what was written.
|
|
7
|
+
*/
|
|
8
|
+
export class FakeFs {
|
|
9
|
+
files;
|
|
10
|
+
dirs;
|
|
11
|
+
reads = [];
|
|
12
|
+
writes = [];
|
|
13
|
+
mkdirs = [];
|
|
14
|
+
madeDirs = new Set();
|
|
15
|
+
constructor(files = {}, dirs = {}) {
|
|
16
|
+
this.files = files;
|
|
17
|
+
this.dirs = dirs;
|
|
18
|
+
}
|
|
19
|
+
exists(path) {
|
|
20
|
+
this.reads.push(path);
|
|
21
|
+
return path in this.files || this.madeDirs.has(path);
|
|
22
|
+
}
|
|
23
|
+
readText(path) {
|
|
24
|
+
this.reads.push(path);
|
|
25
|
+
return this.files[path] ?? null;
|
|
26
|
+
}
|
|
27
|
+
readdir(path) {
|
|
28
|
+
this.reads.push(path);
|
|
29
|
+
// Seeded names first, then immediate child dirs created via mkdirp — so a
|
|
30
|
+
// dir made DURING the test is visible to a later listing, as NodeFs would
|
|
31
|
+
// be (plan 015: capture mkdirps a bucket; a later sweep readdirs its parent).
|
|
32
|
+
// Probes tolerate Windows-shaped paths; registered state is canonical
|
|
33
|
+
// POSIX (plan 017 — Windows-shaped-input sensors run on every OS).
|
|
34
|
+
const posixPath = path.replace(/\\/g, '/');
|
|
35
|
+
const names = [...(this.dirs[path] ?? this.dirs[posixPath] ?? [])];
|
|
36
|
+
const prefix = posixPath.endsWith('/') ? posixPath : `${posixPath}/`;
|
|
37
|
+
for (const dir of this.madeDirs) {
|
|
38
|
+
if (dir.startsWith(prefix)) {
|
|
39
|
+
const name = dir.slice(prefix.length).split(/[\\/]/)[0];
|
|
40
|
+
if (name && !names.includes(name))
|
|
41
|
+
names.push(name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return names;
|
|
45
|
+
}
|
|
46
|
+
mkdirp(path) {
|
|
47
|
+
this.mkdirs.push(path);
|
|
48
|
+
// Register each ancestor segment so exists() models a recursive create
|
|
49
|
+
// (matches NodeFs.mkdirSync({ recursive: true }); F001). Segments split on
|
|
50
|
+
// either separator and are stored in canonical POSIX form (plan 017).
|
|
51
|
+
const parts = path.split(/[\\/]/);
|
|
52
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
53
|
+
const seg = parts.slice(0, i).join('/');
|
|
54
|
+
if (seg)
|
|
55
|
+
this.madeDirs.add(seg);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
writeText(path, contents) {
|
|
59
|
+
this.writes.push(path);
|
|
60
|
+
this.files[path] = contents;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=fake-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-fs.js","sourceRoot":"","sources":["../../../src/adapters/fs/fake-fs.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,MAAM;IAOE;IACA;IAPV,KAAK,GAAa,EAAE,CAAC;IACrB,MAAM,GAAa,EAAE,CAAC;IACtB,MAAM,GAAa,EAAE,CAAC;IACd,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAE9C,YACmB,QAAgC,EAAE,EAClC,OAAiC,EAAE;QADnC,UAAK,GAAL,KAAK,CAA6B;QAClC,SAAI,GAAJ,IAAI,CAA+B;IACnD,CAAC;IAEJ,MAAM,CAAC,IAAY;QACjB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAClC,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,0EAA0E;QAC1E,0EAA0E;QAC1E,8EAA8E;QAC9E,sEAAsE;QACtE,mEAAmE;QACnE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC;QACrE,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACxD,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,uEAAuE;QACvE,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,GAAG;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,QAAgB;QACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;IAC9B,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem port — the side effect `doctor`/config reads sit behind, plus the
|
|
3
|
+
* writes the scaffolder (`harness new`, plan 006) needs.
|
|
4
|
+
*
|
|
5
|
+
* Reads (`exists`/`readText`/`readdir`) and writes (`mkdirp`/`writeText`) are
|
|
6
|
+
* injected so services stay unit-testable with `FakeFs` and never import
|
|
7
|
+
* `node:fs`.
|
|
8
|
+
*/
|
|
9
|
+
export interface FsPort {
|
|
10
|
+
/** True if a path exists on disk. */
|
|
11
|
+
exists(path: string): boolean;
|
|
12
|
+
/** File contents as UTF-8, or null if missing/unreadable (never throws). */
|
|
13
|
+
readText(path: string): string | null;
|
|
14
|
+
/** Entry names directly inside a directory, or `[]` if missing/unreadable (never throws). */
|
|
15
|
+
readdir(path: string): string[];
|
|
16
|
+
/** Recursively create a directory (no-op if it already exists). For the scaffolder. */
|
|
17
|
+
mkdirp(path: string): void;
|
|
18
|
+
/** Write UTF-8 text to a path, overwriting. Caller ensures the parent dir exists (mkdirp). */
|
|
19
|
+
writeText(path: string, contents: string): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-port.js","sourceRoot":"","sources":["../../../src/adapters/fs/fs-port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FsPort } from './fs-port.js';
|
|
2
|
+
/** Real filesystem — the only place `node:fs` is touched. */
|
|
3
|
+
export declare class NodeFs implements FsPort {
|
|
4
|
+
exists(path: string): boolean;
|
|
5
|
+
readText(path: string): string | null;
|
|
6
|
+
readdir(path: string): string[];
|
|
7
|
+
mkdirp(path: string): void;
|
|
8
|
+
writeText(path: string, contents: string): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
/** Real filesystem — the only place `node:fs` is touched. */
|
|
3
|
+
export class NodeFs {
|
|
4
|
+
exists(path) {
|
|
5
|
+
return existsSync(path);
|
|
6
|
+
}
|
|
7
|
+
readText(path) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(path, 'utf8');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
readdir(path) {
|
|
16
|
+
try {
|
|
17
|
+
return readdirSync(path);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
mkdirp(path) {
|
|
24
|
+
mkdirSync(path, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
writeText(path, contents) {
|
|
27
|
+
writeFileSync(path, contents, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=node-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-fs.js","sourceRoot":"","sources":["../../../src/adapters/fs/node-fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG1F,6DAA6D;AAC7D,MAAM,OAAO,MAAM;IACjB,MAAM,CAAC,IAAY;QACjB,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC;YACH,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,QAAgB;QACtC,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
/** Real git access — wraps `git rev-parse` (read-only, informational). */
|
|
3
|
+
export class ExecGit {
|
|
4
|
+
isRepo() {
|
|
5
|
+
const result = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
6
|
+
encoding: 'utf8',
|
|
7
|
+
});
|
|
8
|
+
return result.status === 0 && result.stdout.trim() === 'true';
|
|
9
|
+
}
|
|
10
|
+
currentBranch() {
|
|
11
|
+
const result = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
12
|
+
encoding: 'utf8',
|
|
13
|
+
});
|
|
14
|
+
if (result.status !== 0) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const branch = result.stdout.trim();
|
|
18
|
+
return branch && branch !== 'HEAD' ? branch : null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=exec-git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec-git.js","sourceRoot":"","sources":["../../../src/adapters/git/exec-git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,0EAA0E;AAC1E,MAAM,OAAO,OAAO;IAClB,MAAM;QACJ,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,EAAE;YACtE,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IAChE,CAAC;IAED,aAAa;QACX,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE;YACrE,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACpC,OAAO,MAAM,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GitPort } from './git-port.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic git for tests. Seeded with repo/branch state; records each
|
|
4
|
+
* method called on `calls` (fakes over mocks).
|
|
5
|
+
*/
|
|
6
|
+
export declare class FakeGit implements GitPort {
|
|
7
|
+
private readonly state;
|
|
8
|
+
readonly calls: string[];
|
|
9
|
+
constructor(state?: {
|
|
10
|
+
isRepo?: boolean;
|
|
11
|
+
branch?: string | null;
|
|
12
|
+
});
|
|
13
|
+
isRepo(): boolean;
|
|
14
|
+
currentBranch(): string | null;
|
|
15
|
+
}
|