@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,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUBLIC verb contract — the types an extension author imports.
|
|
3
|
+
*
|
|
4
|
+
* Authors `import type { HarnessVerb } from '@ai-substrate/engineering-harness/contract'`
|
|
5
|
+
* (resolved by the `exports["./contract"]` map added in T023). Everything here is
|
|
6
|
+
* **types only**, so it is erased at runtime — even a plain `.js` extension can
|
|
7
|
+
* reference these via JSDoc with no runtime dependency on the core (plan D4).
|
|
8
|
+
*
|
|
9
|
+
* The core provides the implementation: it discovers + loads each extension's
|
|
10
|
+
* default export (a {@link HarnessVerb} or array), registers one commander
|
|
11
|
+
* subcommand per verb, builds the {@link VerbContext}, and finalizes the
|
|
12
|
+
* returned {@link VerbResult} into a canonical Envelope (adding `command` +
|
|
13
|
+
* `timestamp` and mapping `status` → exit code). Authors return intent; the
|
|
14
|
+
* kernel stays the sole owner of `command`/`timestamp`/exit/`process.exit`.
|
|
15
|
+
*/
|
|
16
|
+
import type { ExecResult } from '../../adapters/exec/exec-port.js';
|
|
17
|
+
import type { Evidence } from '../../output/envelope.js';
|
|
18
|
+
import type { HarnessRecordType } from '../record/contract.js';
|
|
19
|
+
export type { ExecResult } from '../../adapters/exec/exec-port.js';
|
|
20
|
+
export type { Evidence } from '../../output/envelope.js';
|
|
21
|
+
export type { HarnessRecordType } from '../record/contract.js';
|
|
22
|
+
/** The four states a verb can report — mirrors the kernel's Envelope statuses. */
|
|
23
|
+
export type VerbStatus = 'ok' | 'degraded' | 'unconfigured' | 'error';
|
|
24
|
+
export interface VerbOption {
|
|
25
|
+
/** commander-style flags, e.g. '--name <name>' or '-f, --force'. */
|
|
26
|
+
flags: string;
|
|
27
|
+
description: string;
|
|
28
|
+
defaultValue?: string | boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface VerbArg {
|
|
31
|
+
/** commander-style, e.g. '<target>' (required) or '[target]' (optional). */
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* What a handler RETURNS. The core finalizes it into a full Envelope (adds
|
|
37
|
+
* `command` = the verb name + `timestamp` from the clock, maps status → exit).
|
|
38
|
+
* `next_action` is REQUIRED by the core for any non-`ok` status (P5).
|
|
39
|
+
*/
|
|
40
|
+
export interface VerbResult {
|
|
41
|
+
status: VerbStatus;
|
|
42
|
+
data?: unknown;
|
|
43
|
+
error?: {
|
|
44
|
+
code: string;
|
|
45
|
+
message: string;
|
|
46
|
+
details?: unknown;
|
|
47
|
+
};
|
|
48
|
+
evidence?: Evidence[];
|
|
49
|
+
next_action?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Injected per-invocation by the core from its ports. Authors never build this.
|
|
53
|
+
* The shapes are inlined (not adapter imports) so the published contract is
|
|
54
|
+
* self-contained.
|
|
55
|
+
*/
|
|
56
|
+
export interface VerbContext {
|
|
57
|
+
/** The developer-repo cwd discovery resolved against. */
|
|
58
|
+
cwd: string;
|
|
59
|
+
/** Parsed positional args, by arg name. */
|
|
60
|
+
args: Record<string, string | undefined>;
|
|
61
|
+
/** Parsed flags, by camelCased name. */
|
|
62
|
+
options: Record<string, unknown>;
|
|
63
|
+
/** Run a REAL repo command (P8 — "wrap, don't rebuild"). cwd defaults to ctx.cwd. */
|
|
64
|
+
exec(command: string, args?: string[], opts?: {
|
|
65
|
+
cwd?: string;
|
|
66
|
+
}): Promise<ExecResult>;
|
|
67
|
+
fs: {
|
|
68
|
+
exists(path: string): boolean;
|
|
69
|
+
readText(path: string): string | null;
|
|
70
|
+
readdir(path: string): string[];
|
|
71
|
+
};
|
|
72
|
+
env: {
|
|
73
|
+
get(name: string): string | undefined;
|
|
74
|
+
};
|
|
75
|
+
git: {
|
|
76
|
+
isRepo(): boolean;
|
|
77
|
+
currentBranch(): string | null;
|
|
78
|
+
};
|
|
79
|
+
clock: {
|
|
80
|
+
nowIso(): string;
|
|
81
|
+
};
|
|
82
|
+
ok<T>(data: T, opts?: {
|
|
83
|
+
evidence?: Evidence[];
|
|
84
|
+
next_action?: string;
|
|
85
|
+
}): VerbResult;
|
|
86
|
+
degraded<T>(data: T, next_action: string, opts?: {
|
|
87
|
+
evidence?: Evidence[];
|
|
88
|
+
}): VerbResult;
|
|
89
|
+
unconfigured(next_action: string, opts?: {
|
|
90
|
+
data?: unknown;
|
|
91
|
+
}): VerbResult;
|
|
92
|
+
error(code: string, message: string, opts?: {
|
|
93
|
+
details?: unknown;
|
|
94
|
+
next_action?: string;
|
|
95
|
+
}): VerbResult;
|
|
96
|
+
}
|
|
97
|
+
/** The verb an extension declares. */
|
|
98
|
+
export interface HarnessVerb {
|
|
99
|
+
/**
|
|
100
|
+
* Discriminator — distinguishes a verb export from a {@link HarnessRecordType}.
|
|
101
|
+
* OPTIONAL and defaults to `'verb'` when absent, so every existing verb
|
|
102
|
+
* extension (which never set it) keeps working unchanged.
|
|
103
|
+
*/
|
|
104
|
+
kind?: 'verb';
|
|
105
|
+
/** e.g. 'build' → `harness build`. */
|
|
106
|
+
name: string;
|
|
107
|
+
/** One line — shown in `help`'s list + `doctor`. */
|
|
108
|
+
summary: string;
|
|
109
|
+
/** Longer body shown by `harness <verb> --help`. */
|
|
110
|
+
description?: string;
|
|
111
|
+
options?: VerbOption[];
|
|
112
|
+
args?: VerbArg[];
|
|
113
|
+
run(ctx: VerbContext): VerbResult | Promise<VerbResult>;
|
|
114
|
+
}
|
|
115
|
+
/** A `.harness/extensions/*` entry's DEFAULT export (verb-only — kept for back-compat). */
|
|
116
|
+
export type ExtensionExport = HarnessVerb | HarnessVerb[];
|
|
117
|
+
/**
|
|
118
|
+
* The widened default-export union: a `.harness/extensions/*` file may declare a
|
|
119
|
+
* verb, a record type, or an array mixing both. The loader routes each by `kind`
|
|
120
|
+
* (`kind:'record'` → record registry; absent/`'verb'` → verb registry).
|
|
121
|
+
*/
|
|
122
|
+
export type HarnessExtensionExport = HarnessVerb | HarnessRecordType | Array<HarnessVerb | HarnessRecordType>;
|
|
123
|
+
/** What `doctor` enumerates per discovered extension file (P7). */
|
|
124
|
+
export interface ExtensionRecord {
|
|
125
|
+
/** Resolved absolute path. */
|
|
126
|
+
entryPath: string;
|
|
127
|
+
status: 'loaded' | 'failed' | 'conflict';
|
|
128
|
+
/** Declared verbs accepted from this file (empty when `failed` or record-only). */
|
|
129
|
+
verbs: HarnessVerb[];
|
|
130
|
+
/** Declared record types accepted from this file (present only when non-empty). */
|
|
131
|
+
recordTypes?: HarnessRecordType[];
|
|
132
|
+
/** Load/validation message (present when status !== 'loaded'). */
|
|
133
|
+
error?: string;
|
|
134
|
+
/** For 'conflict': the VERB name(s) shadowed by a core command or earlier extension. */
|
|
135
|
+
shadows?: string[];
|
|
136
|
+
/** For 'conflict': the RECORD-TYPE name(s) shadowed by a core type or earlier extension. */
|
|
137
|
+
recordShadows?: string[];
|
|
138
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUBLIC verb contract — the types an extension author imports.
|
|
3
|
+
*
|
|
4
|
+
* Authors `import type { HarnessVerb } from '@ai-substrate/engineering-harness/contract'`
|
|
5
|
+
* (resolved by the `exports["./contract"]` map added in T023). Everything here is
|
|
6
|
+
* **types only**, so it is erased at runtime — even a plain `.js` extension can
|
|
7
|
+
* reference these via JSDoc with no runtime dependency on the core (plan D4).
|
|
8
|
+
*
|
|
9
|
+
* The core provides the implementation: it discovers + loads each extension's
|
|
10
|
+
* default export (a {@link HarnessVerb} or array), registers one commander
|
|
11
|
+
* subcommand per verb, builds the {@link VerbContext}, and finalizes the
|
|
12
|
+
* returned {@link VerbResult} into a canonical Envelope (adding `command` +
|
|
13
|
+
* `timestamp` and mapping `status` → exit code). Authors return intent; the
|
|
14
|
+
* kernel stays the sole owner of `command`/`timestamp`/exit/`process.exit`.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../src/services/extensions/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { FsPort } from '../../adapters/fs/fs-port.js';
|
|
2
|
+
import type { ProcessPort } from '../../adapters/process/process-port.js';
|
|
3
|
+
/** A directory entry discovery refused, with the reason doctor should surface. */
|
|
4
|
+
export interface RejectedExtension {
|
|
5
|
+
/** Resolved absolute path of the refused entry. */
|
|
6
|
+
path: string;
|
|
7
|
+
/** Human-actionable reason (becomes the E143 record detail). */
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
/** What one discovery pass yields: loadable entry paths + refused entries (plan 014 D1). */
|
|
11
|
+
export interface DiscoveryResult {
|
|
12
|
+
/** Sorted, deduped entry-file paths the loader should import. */
|
|
13
|
+
candidates: string[];
|
|
14
|
+
/** Entries refused with a reason (e.g. unsupported flat layout) — never loaded. */
|
|
15
|
+
rejected: RejectedExtension[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Scan `<cwd>/.harness/extensions/` ONE level and resolve each entry to the
|
|
19
|
+
* extension entry file the loader should import (WS-A Decision 4; folder-only
|
|
20
|
+
* since plan 014). Pure of direct Node I/O — the directory listing comes from
|
|
21
|
+
* `FsPort.readdir`, the cwd from `ProcessPort.cwd`, so the whole thing is
|
|
22
|
+
* unit-testable with fakes.
|
|
23
|
+
*
|
|
24
|
+
* Rules (plan 014 AC-6 / D1):
|
|
25
|
+
* - An extension is a FOLDER (a little package). Per folder, the first hit of
|
|
26
|
+
* `package.json` `harness.extensions[]` manifest → `extension.ts` →
|
|
27
|
+
* `extension.js` → `index.ts` → `index.js` is the entry. `.tsx`/`.mjs`/`.cjs`
|
|
28
|
+
* entries are reachable only via the manifest.
|
|
29
|
+
* - A direct `*.ts|*.tsx|*.mjs|*.cjs|*.js` file is the retired flat layout →
|
|
30
|
+
* `rejected[]` with reason `unsupported flat layout — move to <name>/extension.ts`
|
|
31
|
+
* (the registry turns each into a `failed` E143 record for doctor).
|
|
32
|
+
* - Other direct files (`README`, `*.md`, …) and unresolvable folders are
|
|
33
|
+
* silently ignored, as before.
|
|
34
|
+
* - Manifest entries that resolve OUTSIDE their own subdir are dropped (no
|
|
35
|
+
* `../escape.ts` path traversal — lexical containment only; see the realpath
|
|
36
|
+
* note below for symlinks).
|
|
37
|
+
* - Entries are processed in **sorted** name order (stable "first wins").
|
|
38
|
+
* - Candidates are **deduped** by POSIX-normalized logical path (first
|
|
39
|
+
* occurrence kept; case-folded on win32 — see `dedupeKey`).
|
|
40
|
+
* - Absent / empty dir → empty result (never an error).
|
|
41
|
+
*
|
|
42
|
+
* Discovery is the SINGLE POSIX ORIGIN for extension paths (plan 017): the cwd
|
|
43
|
+
* is converted via `toPosix` at the boundary and every emitted `entryPath` /
|
|
44
|
+
* `folder` / rejected `path` is a logical POSIX path, so downstream consumers
|
|
45
|
+
* (doctor, instructions, registry) never re-normalize per site.
|
|
46
|
+
*
|
|
47
|
+
* NOTE: dedup + containment are lexical in POSIX space (posix-path helper —
|
|
48
|
+
* never `resolve`, which corrupts drive-letter paths); symlink-following (true
|
|
49
|
+
* realpath) is deferred — it would need a new `FsPort.realpath` capability, so
|
|
50
|
+
* the `../escape` guard is lexical and does NOT stop a symlink inside the
|
|
51
|
+
* subdir from pointing elsewhere.
|
|
52
|
+
*/
|
|
53
|
+
export declare function discoverExtensions(fs: FsPort, proc: ProcessPort): DiscoveryResult;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { dedupeKey, isWithin, posixJoin, toPosix } from '../shared/posix-path.js';
|
|
2
|
+
const EXTENSIONS_DIR = ['.harness', 'extensions'];
|
|
3
|
+
const CODE_FILE = /\.(ts|tsx|mjs|cjs|js)$/;
|
|
4
|
+
/** Per-folder convention chain, probed in order after the manifest (plan 014 AC-6). */
|
|
5
|
+
const ENTRY_CHAIN = ['extension.ts', 'extension.js', 'index.ts', 'index.js'];
|
|
6
|
+
/**
|
|
7
|
+
* Scan `<cwd>/.harness/extensions/` ONE level and resolve each entry to the
|
|
8
|
+
* extension entry file the loader should import (WS-A Decision 4; folder-only
|
|
9
|
+
* since plan 014). Pure of direct Node I/O — the directory listing comes from
|
|
10
|
+
* `FsPort.readdir`, the cwd from `ProcessPort.cwd`, so the whole thing is
|
|
11
|
+
* unit-testable with fakes.
|
|
12
|
+
*
|
|
13
|
+
* Rules (plan 014 AC-6 / D1):
|
|
14
|
+
* - An extension is a FOLDER (a little package). Per folder, the first hit of
|
|
15
|
+
* `package.json` `harness.extensions[]` manifest → `extension.ts` →
|
|
16
|
+
* `extension.js` → `index.ts` → `index.js` is the entry. `.tsx`/`.mjs`/`.cjs`
|
|
17
|
+
* entries are reachable only via the manifest.
|
|
18
|
+
* - A direct `*.ts|*.tsx|*.mjs|*.cjs|*.js` file is the retired flat layout →
|
|
19
|
+
* `rejected[]` with reason `unsupported flat layout — move to <name>/extension.ts`
|
|
20
|
+
* (the registry turns each into a `failed` E143 record for doctor).
|
|
21
|
+
* - Other direct files (`README`, `*.md`, …) and unresolvable folders are
|
|
22
|
+
* silently ignored, as before.
|
|
23
|
+
* - Manifest entries that resolve OUTSIDE their own subdir are dropped (no
|
|
24
|
+
* `../escape.ts` path traversal — lexical containment only; see the realpath
|
|
25
|
+
* note below for symlinks).
|
|
26
|
+
* - Entries are processed in **sorted** name order (stable "first wins").
|
|
27
|
+
* - Candidates are **deduped** by POSIX-normalized logical path (first
|
|
28
|
+
* occurrence kept; case-folded on win32 — see `dedupeKey`).
|
|
29
|
+
* - Absent / empty dir → empty result (never an error).
|
|
30
|
+
*
|
|
31
|
+
* Discovery is the SINGLE POSIX ORIGIN for extension paths (plan 017): the cwd
|
|
32
|
+
* is converted via `toPosix` at the boundary and every emitted `entryPath` /
|
|
33
|
+
* `folder` / rejected `path` is a logical POSIX path, so downstream consumers
|
|
34
|
+
* (doctor, instructions, registry) never re-normalize per site.
|
|
35
|
+
*
|
|
36
|
+
* NOTE: dedup + containment are lexical in POSIX space (posix-path helper —
|
|
37
|
+
* never `resolve`, which corrupts drive-letter paths); symlink-following (true
|
|
38
|
+
* realpath) is deferred — it would need a new `FsPort.realpath` capability, so
|
|
39
|
+
* the `../escape` guard is lexical and does NOT stop a symlink inside the
|
|
40
|
+
* subdir from pointing elsewhere.
|
|
41
|
+
*/
|
|
42
|
+
export function discoverExtensions(fs, proc) {
|
|
43
|
+
const base = posixJoin(toPosix(proc.cwd()), ...EXTENSIONS_DIR);
|
|
44
|
+
const entries = fs.readdir(base);
|
|
45
|
+
if (entries.length === 0) {
|
|
46
|
+
return { candidates: [], rejected: [] };
|
|
47
|
+
}
|
|
48
|
+
const candidates = [];
|
|
49
|
+
const rejected = [];
|
|
50
|
+
for (const entry of [...entries].sort()) {
|
|
51
|
+
const entryPath = posixJoin(base, entry);
|
|
52
|
+
if (CODE_FILE.test(entry)) {
|
|
53
|
+
const name = entry.replace(CODE_FILE, '');
|
|
54
|
+
rejected.push({
|
|
55
|
+
path: entryPath,
|
|
56
|
+
reason: `unsupported flat layout — move to ${name}/extension.ts`,
|
|
57
|
+
});
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
candidates.push(...resolveSubdir(fs, entryPath));
|
|
61
|
+
}
|
|
62
|
+
return { candidates: dedupeByAbsolutePath(candidates), rejected };
|
|
63
|
+
}
|
|
64
|
+
/** Resolve a sub-directory to its entry file(s): manifest → extension.ts → extension.js → index.ts → index.js → none. */
|
|
65
|
+
function resolveSubdir(fs, dir) {
|
|
66
|
+
const manifestPaths = readManifest(fs, dir);
|
|
67
|
+
if (manifestPaths.length > 0) {
|
|
68
|
+
return manifestPaths;
|
|
69
|
+
}
|
|
70
|
+
for (const entry of ENTRY_CHAIN) {
|
|
71
|
+
const entryPath = posixJoin(dir, entry);
|
|
72
|
+
if (fs.exists(entryPath)) {
|
|
73
|
+
return [entryPath];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
/** Read `<dir>/package.json` and return resolved `harness.extensions[]` paths, or []. */
|
|
79
|
+
function readManifest(fs, dir) {
|
|
80
|
+
const pkgPath = posixJoin(dir, 'package.json');
|
|
81
|
+
if (!fs.exists(pkgPath)) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const raw = fs.readText(pkgPath);
|
|
85
|
+
if (raw === null) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(raw);
|
|
90
|
+
const list = parsed.harness?.extensions;
|
|
91
|
+
if (!Array.isArray(list)) {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
return list
|
|
95
|
+
.filter((entry) => typeof entry === 'string')
|
|
96
|
+
.map((rel) => posixJoin(dir, rel))
|
|
97
|
+
.filter((candidate) => isWithin(dir, candidate));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Keep the first occurrence of each logical path (POSIX-normalized `dedupeKey`; preserves order). */
|
|
104
|
+
function dedupeByAbsolutePath(paths) {
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
const result = [];
|
|
107
|
+
for (const path of paths) {
|
|
108
|
+
const key = dedupeKey(path);
|
|
109
|
+
if (!seen.has(key)) {
|
|
110
|
+
seen.add(key);
|
|
111
|
+
result.push(path);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/services/extensions/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ModuleLoaderPort } from '../../adapters/loader/module-loader-port.js';
|
|
2
|
+
import { type ExtensionRecordType } from '../record/registry.js';
|
|
3
|
+
import type { ExtensionRecord, HarnessVerb } from './contract.js';
|
|
4
|
+
/** The assembled verb surface + the per-extension provenance `doctor` enumerates. */
|
|
5
|
+
export interface VerbRegistry {
|
|
6
|
+
/** Verbs accepted into the command surface (deduped, core names excluded). */
|
|
7
|
+
verbs: HarnessVerb[];
|
|
8
|
+
/** One record per discovered extension file: loaded / failed / conflict. */
|
|
9
|
+
records: ExtensionRecord[];
|
|
10
|
+
}
|
|
11
|
+
/** The full extension surface from ONE discovery pass: verbs + record types + provenance. */
|
|
12
|
+
export interface ExtensionRegistry {
|
|
13
|
+
/** Verbs accepted into the command surface (deduped, reserved/claimed excluded). */
|
|
14
|
+
verbs: HarnessVerb[];
|
|
15
|
+
/** Record types accepted from extensions, paired with their discovery path (de-conflicted). */
|
|
16
|
+
recordTypes: ExtensionRecordType[];
|
|
17
|
+
/** One record per discovered extension file: loaded / failed / conflict. */
|
|
18
|
+
records: ExtensionRecord[];
|
|
19
|
+
}
|
|
20
|
+
/** Options for the single-pass loader: which verb names + record-type names are reserved. */
|
|
21
|
+
export interface ExtensionRegistryOptions {
|
|
22
|
+
/** Verb names an extension may NOT claim (core commands). Defaults to {@link RESERVED_NAMES}. */
|
|
23
|
+
reservedVerbs?: ReadonlySet<string>;
|
|
24
|
+
/** Record-type names an extension may NOT claim (core types). Defaults to empty. */
|
|
25
|
+
reservedRecordTypes?: ReadonlySet<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Entries discovery refused (e.g. unsupported flat layout). Each becomes a
|
|
28
|
+
* synthesized `failed` record (E143) with NO load attempt, so doctor surfaces
|
|
29
|
+
* it through the existing record rendering (plan 014 D1).
|
|
30
|
+
*/
|
|
31
|
+
rejected?: readonly {
|
|
32
|
+
path: string;
|
|
33
|
+
reason: string;
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Core command names an extension may NOT shadow. `help`/`doctor`/`new`/`docs`/
|
|
38
|
+
* `skills`/`record`/`instructions`/`observe`/`init` are all reserved core
|
|
39
|
+
* commands (`instructions` reserved since plan 014 — the agent-briefing act;
|
|
40
|
+
* `observe` since plan 015 — the friction-capture act; `init` since plan 008
|
|
41
|
+
* FX001 — the governance-doc inception writer).
|
|
42
|
+
*/
|
|
43
|
+
export declare const RESERVED_NAMES: ReadonlySet<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Build the full extension registry from discovered candidate paths in ONE load
|
|
46
|
+
* pass (already sorted by discovery, so "first wins" is deterministic). Each file
|
|
47
|
+
* is loaded in isolation: a load throw or malformed default export becomes a
|
|
48
|
+
* `failed` record (`E140`) and never breaks the others. Each declared export is
|
|
49
|
+
* routed by its `kind`:
|
|
50
|
+
* - `kind:'record'` → a {@link HarnessRecordType} candidate for the record registry;
|
|
51
|
+
* - absent/`'verb'` → a {@link HarnessVerb} candidate for the command surface.
|
|
52
|
+
* A verb name claimed by a reserved core command / earlier extension, or a record
|
|
53
|
+
* type claimed by a reserved core type / earlier extension, is a `conflict`
|
|
54
|
+
* (`E142`); ALL shadowed duplicates are recorded (verb shadows in `shadows`,
|
|
55
|
+
* record-type shadows in `recordShadows`) and none of them are registered.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildExtensionRegistry(candidates: string[], loader: ModuleLoaderPort, options?: ExtensionRegistryOptions): Promise<ExtensionRegistry>;
|
|
58
|
+
/**
|
|
59
|
+
* Verb-only projection of {@link buildExtensionRegistry} — the historical surface
|
|
60
|
+
* many call sites + tests use. Record-kinded exports (if any) are routed away and
|
|
61
|
+
* never registered as verbs; the per-file provenance is preserved.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildVerbRegistry(candidates: string[], loader: ModuleLoaderPort, reserved?: ReadonlySet<string>): Promise<VerbRegistry>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { ErrorCodes } from '../../output/error-codes.js';
|
|
2
|
+
import { verbShapeIssues } from '../config/load-config.js';
|
|
3
|
+
import { recordTypeShapeIssues } from '../record/registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Core command names an extension may NOT shadow. `help`/`doctor`/`new`/`docs`/
|
|
6
|
+
* `skills`/`record`/`instructions`/`observe`/`init` are all reserved core
|
|
7
|
+
* commands (`instructions` reserved since plan 014 — the agent-briefing act;
|
|
8
|
+
* `observe` since plan 015 — the friction-capture act; `init` since plan 008
|
|
9
|
+
* FX001 — the governance-doc inception writer).
|
|
10
|
+
*/
|
|
11
|
+
export const RESERVED_NAMES = new Set([
|
|
12
|
+
'help',
|
|
13
|
+
'doctor',
|
|
14
|
+
'new',
|
|
15
|
+
'docs',
|
|
16
|
+
'skills',
|
|
17
|
+
'record',
|
|
18
|
+
'instructions',
|
|
19
|
+
'observe',
|
|
20
|
+
'init',
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Build the full extension registry from discovered candidate paths in ONE load
|
|
24
|
+
* pass (already sorted by discovery, so "first wins" is deterministic). Each file
|
|
25
|
+
* is loaded in isolation: a load throw or malformed default export becomes a
|
|
26
|
+
* `failed` record (`E140`) and never breaks the others. Each declared export is
|
|
27
|
+
* routed by its `kind`:
|
|
28
|
+
* - `kind:'record'` → a {@link HarnessRecordType} candidate for the record registry;
|
|
29
|
+
* - absent/`'verb'` → a {@link HarnessVerb} candidate for the command surface.
|
|
30
|
+
* A verb name claimed by a reserved core command / earlier extension, or a record
|
|
31
|
+
* type claimed by a reserved core type / earlier extension, is a `conflict`
|
|
32
|
+
* (`E142`); ALL shadowed duplicates are recorded (verb shadows in `shadows`,
|
|
33
|
+
* record-type shadows in `recordShadows`) and none of them are registered.
|
|
34
|
+
*/
|
|
35
|
+
export async function buildExtensionRegistry(candidates, loader, options = {}) {
|
|
36
|
+
const reservedVerbs = options.reservedVerbs ?? RESERVED_NAMES;
|
|
37
|
+
const reservedRecordTypes = options.reservedRecordTypes ?? new Set();
|
|
38
|
+
const records = (options.rejected ?? []).map((entry) => failed(entry.path, entry.reason, ErrorCodes.EXTENSION_FLAT_LAYOUT));
|
|
39
|
+
const verbs = [];
|
|
40
|
+
const recordTypes = [];
|
|
41
|
+
const claimedVerbs = new Set();
|
|
42
|
+
const claimedRecordTypes = new Set();
|
|
43
|
+
for (const entryPath of candidates) {
|
|
44
|
+
let mod;
|
|
45
|
+
try {
|
|
46
|
+
mod = await loader.load(entryPath);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
records.push(failed(entryPath, errorMessage(err)));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const declared = coerceExports(mod);
|
|
53
|
+
if (declared === null) {
|
|
54
|
+
records.push(failed(entryPath, 'default export is not a HarnessVerb/HarnessRecordType (or array of them)'));
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Route each declared export by `kind`.
|
|
58
|
+
const verbDecls = [];
|
|
59
|
+
const recordDecls = [];
|
|
60
|
+
for (const entry of declared) {
|
|
61
|
+
if (isRecordKind(entry)) {
|
|
62
|
+
recordDecls.push(entry);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
verbDecls.push(entry);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// File-level shape validation (one bad export fails the whole file, like verbs always did).
|
|
69
|
+
const shapeProblems = [
|
|
70
|
+
...verbDecls.flatMap(verbShapeIssues),
|
|
71
|
+
...recordDecls.flatMap(recordTypeShapeIssues),
|
|
72
|
+
];
|
|
73
|
+
if (shapeProblems.length > 0) {
|
|
74
|
+
records.push(failed(entryPath, shapeProblems.join('; ')));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Acceptance + conflict collection (reserved/claimed → shadowed, never registered).
|
|
78
|
+
const acceptedVerbs = [];
|
|
79
|
+
const acceptedRecords = [];
|
|
80
|
+
const verbShadows = [];
|
|
81
|
+
const recordShadows = [];
|
|
82
|
+
for (const verb of verbDecls) {
|
|
83
|
+
if (reservedVerbs.has(verb.name) || claimedVerbs.has(verb.name)) {
|
|
84
|
+
verbShadows.push(verb.name);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
claimedVerbs.add(verb.name);
|
|
88
|
+
acceptedVerbs.push(verb);
|
|
89
|
+
verbs.push(verb);
|
|
90
|
+
}
|
|
91
|
+
for (const rt of recordDecls) {
|
|
92
|
+
if (reservedRecordTypes.has(rt.type) || claimedRecordTypes.has(rt.type)) {
|
|
93
|
+
recordShadows.push(rt.type);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
claimedRecordTypes.add(rt.type);
|
|
97
|
+
acceptedRecords.push(rt);
|
|
98
|
+
recordTypes.push({ recordType: rt, entryPath });
|
|
99
|
+
}
|
|
100
|
+
const hasShadow = verbShadows.length > 0 || recordShadows.length > 0;
|
|
101
|
+
records.push(hasShadow
|
|
102
|
+
? {
|
|
103
|
+
entryPath,
|
|
104
|
+
status: 'conflict',
|
|
105
|
+
verbs: acceptedVerbs,
|
|
106
|
+
...(acceptedRecords.length > 0 && { recordTypes: acceptedRecords }),
|
|
107
|
+
...(verbShadows.length > 0 && { shadows: verbShadows }),
|
|
108
|
+
...(recordShadows.length > 0 && { recordShadows }),
|
|
109
|
+
error: conflictError(verbShadows, recordShadows),
|
|
110
|
+
}
|
|
111
|
+
: {
|
|
112
|
+
entryPath,
|
|
113
|
+
status: 'loaded',
|
|
114
|
+
verbs: acceptedVerbs,
|
|
115
|
+
...(acceptedRecords.length > 0 && { recordTypes: acceptedRecords }),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return { verbs, recordTypes, records };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Verb-only projection of {@link buildExtensionRegistry} — the historical surface
|
|
122
|
+
* many call sites + tests use. Record-kinded exports (if any) are routed away and
|
|
123
|
+
* never registered as verbs; the per-file provenance is preserved.
|
|
124
|
+
*/
|
|
125
|
+
export async function buildVerbRegistry(candidates, loader, reserved = RESERVED_NAMES) {
|
|
126
|
+
const registry = await buildExtensionRegistry(candidates, loader, { reservedVerbs: reserved });
|
|
127
|
+
return { verbs: registry.verbs, records: registry.records };
|
|
128
|
+
}
|
|
129
|
+
/** True when a declared export is a record type (`kind:'record'`). */
|
|
130
|
+
function isRecordKind(entry) {
|
|
131
|
+
return entry.kind === 'record';
|
|
132
|
+
}
|
|
133
|
+
/** Normalise a default export to a list of declared exports, or null if it can't be one. */
|
|
134
|
+
function coerceExports(mod) {
|
|
135
|
+
const list = Array.isArray(mod) ? mod : [mod];
|
|
136
|
+
if (list.length === 0) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (list.some((entry) => entry === null || typeof entry !== 'object')) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return list;
|
|
143
|
+
}
|
|
144
|
+
function conflictError(verbShadows, recordShadows) {
|
|
145
|
+
const parts = [];
|
|
146
|
+
if (verbShadows.length > 0) {
|
|
147
|
+
parts.push(`verb(s) '${verbShadows.join("', '")}'`);
|
|
148
|
+
}
|
|
149
|
+
if (recordShadows.length > 0) {
|
|
150
|
+
parts.push(`record type(s) '${recordShadows.join("', '")}'`);
|
|
151
|
+
}
|
|
152
|
+
return `${ErrorCodes.EXTENSION_VERB_CONFLICT}: ${parts.join(' and ')} already provided (core command/type or earlier extension); duplicate(s) ignored.`;
|
|
153
|
+
}
|
|
154
|
+
function failed(entryPath, reason, code = ErrorCodes.EXTENSION_LOAD_FAILED) {
|
|
155
|
+
return {
|
|
156
|
+
entryPath,
|
|
157
|
+
status: 'failed',
|
|
158
|
+
verbs: [],
|
|
159
|
+
error: `${code}: ${reason}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function errorMessage(err) {
|
|
163
|
+
return err instanceof Error ? err.message : String(err);
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/services/extensions/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Clock } from '../../adapters/clock/clock-port.js';
|
|
2
|
+
import type { EnvPort } from '../../adapters/env/env-port.js';
|
|
3
|
+
import type { ExecPort } from '../../adapters/exec/exec-port.js';
|
|
4
|
+
import type { FsPort } from '../../adapters/fs/fs-port.js';
|
|
5
|
+
import type { GitPort } from '../../adapters/git/git-port.js';
|
|
6
|
+
import { type Envelope } from '../../output/envelope.js';
|
|
7
|
+
import type { HarnessVerb, VerbContext, VerbResult } from './contract.js';
|
|
8
|
+
/** The ports the composition root injects to build a per-invocation `VerbContext`. */
|
|
9
|
+
export interface VerbContextDeps {
|
|
10
|
+
exec: ExecPort;
|
|
11
|
+
fs: FsPort;
|
|
12
|
+
env: EnvPort;
|
|
13
|
+
git: GitPort;
|
|
14
|
+
clock: Clock;
|
|
15
|
+
}
|
|
16
|
+
/** The parsed invocation a verb is called with. */
|
|
17
|
+
export interface VerbInvocation {
|
|
18
|
+
cwd: string;
|
|
19
|
+
args: Record<string, string | undefined>;
|
|
20
|
+
options: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the `VerbContext` an author's handler receives. Surfaces the injected
|
|
24
|
+
* ports (read-mostly) + envelope-helper closures so a verb never imports the
|
|
25
|
+
* kernel. `ctx.exec` adapts the author-facing signature (optional args + cwd) to
|
|
26
|
+
* `ExecPort.run`, defaulting cwd to the invocation cwd (WS-A Decision 3).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildVerbContext(deps: VerbContextDeps, invocation: VerbInvocation): VerbContext;
|
|
29
|
+
/**
|
|
30
|
+
* Finalize what a handler RETURNED into a canonical Envelope: the kernel adds
|
|
31
|
+
* `command` (the verb name) + `timestamp` (from the clock) and maps status →
|
|
32
|
+
* the right constructor, guaranteeing a non-blank `next_action` on every non-`ok`
|
|
33
|
+
* status (P5) even if the author returned a raw object without one — a blank or
|
|
34
|
+
* whitespace-only `next_action` is treated as missing. A JS extension that returns
|
|
35
|
+
* an unknown `status` (outside the typed union) is mapped to an `E141` error
|
|
36
|
+
* Envelope rather than falling through to `undefined`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function finalizeVerbResult(result: VerbResult, name: string, clock: Clock): Envelope;
|
|
39
|
+
/**
|
|
40
|
+
* Invoke a verb's handler with isolation: a returned `VerbResult` is finalized;
|
|
41
|
+
* a thrown error becomes an `E141` error Envelope surfacing the message (never a
|
|
42
|
+
* raw stack trace — honesty, AC-style). Awaits async handlers.
|
|
43
|
+
*/
|
|
44
|
+
export declare function runVerb(verb: HarnessVerb, ctx: VerbContext, clock: Clock): Promise<Envelope>;
|