@open-agent-toolkit/cli 0.2.62 → 0.2.64
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/config-and-local-state.md +1 -0
- package/assets/docs/cli-utilities/configuration.md +10 -8
- package/assets/docs/cli-utilities/project-log.md +16 -0
- package/assets/docs/cli-utilities/tool-packs.md +83 -0
- package/assets/docs/cli-utilities/workflow-gates.md +124 -16
- package/assets/docs/contributing/skills.md +42 -7
- package/assets/docs/provider-sync/commands.md +3 -0
- package/assets/docs/provider-sync/instruction-sync.md +107 -0
- package/assets/docs/provider-sync/manifest-and-drift.md +105 -0
- package/assets/docs/reference/cli-reference.md +16 -6
- package/assets/docs/reference/oat-directory-structure.md +32 -31
- package/assets/docs/workflows/projects/lifecycle.md +45 -0
- package/assets/docs/workflows/projects/picking-up-projects.md +14 -0
- package/assets/docs/workflows/skills/explainer-kit.md +10 -4
- package/assets/docs/workflows/skills/repo-improve.md +15 -1
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/create-agnostic-skill/SKILL.md +9 -7
- package/assets/skills/create-agnostic-skill/references/skill-template.md +9 -7
- package/assets/skills/oat-explainer-kit/SKILL.md +14 -1
- package/assets/skills/oat-explainer-kit/references/config-contract.md +12 -0
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +57 -10
- package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +28 -5
- package/assets/skills/oat-explainer-kit/scripts/probe-recap-seams.mjs +371 -0
- package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +123 -1
- package/assets/skills/oat-project-autonomous/SKILL.md +10 -1
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +122 -121
- package/assets/skills/oat-project-complete/SKILL.md +134 -4
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +122 -121
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +38 -6
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +122 -121
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +122 -121
- package/assets/skills/oat-project-next/SKILL.md +62 -19
- package/assets/skills/oat-project-plan/SKILL.md +31 -5
- package/assets/skills/oat-project-pr-final/SKILL.md +281 -6
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +122 -121
- package/assets/skills/oat-project-progress/SKILL.md +12 -3
- package/assets/skills/oat-project-quick-start/SKILL.md +264 -4
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +122 -121
- package/assets/skills/oat-project-summary/SKILL.md +34 -23
- package/assets/skills/oat-repo-improve/SKILL.md +40 -5
- package/assets/skills/oat-repo-improve/references/plan-template.md +84 -7
- package/dist/agents/canonical/resolve.d.ts.map +1 -1
- package/dist/agents/canonical/resolve.js +25 -4
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +401 -18
- package/dist/commands/doctor/index.d.ts +7 -0
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +10 -2
- package/dist/commands/gate/index.d.ts +3 -1
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +699 -211
- package/dist/commands/init/tools/index.d.ts.map +1 -1
- package/dist/commands/init/tools/index.js +68 -36
- package/dist/commands/init/tools/shared/skill-script-references.d.ts +126 -0
- package/dist/commands/init/tools/shared/skill-script-references.d.ts.map +1 -0
- package/dist/commands/init/tools/shared/skill-script-references.js +311 -0
- package/dist/commands/instructions/instructions.types.d.ts +63 -0
- package/dist/commands/instructions/instructions.types.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.utils.d.ts +51 -2
- package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.utils.js +201 -4
- package/dist/commands/instructions/sync/sync.d.ts.map +1 -1
- package/dist/commands/instructions/sync/sync.js +12 -1
- package/dist/commands/instructions/validate/validate.d.ts.map +1 -1
- package/dist/commands/instructions/validate/validate.js +12 -1
- package/dist/commands/project/log/append.d.ts +195 -0
- package/dist/commands/project/log/append.d.ts.map +1 -1
- package/dist/commands/project/log/append.js +1034 -11
- package/dist/commands/shared/frontmatter.d.ts +51 -0
- package/dist/commands/shared/frontmatter.d.ts.map +1 -1
- package/dist/commands/shared/frontmatter.js +118 -6
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +5 -3
- package/dist/commands/sync/index.d.ts +14 -0
- package/dist/commands/sync/index.d.ts.map +1 -1
- package/dist/commands/sync/index.js +21 -0
- package/dist/commands/tools/info/index.d.ts.map +1 -1
- package/dist/commands/tools/info/index.js +14 -1
- package/dist/commands/tools/info/info-tool.d.ts +12 -0
- package/dist/commands/tools/info/info-tool.d.ts.map +1 -1
- package/dist/commands/tools/info/info-tool.js +54 -6
- package/dist/commands/tools/info/skill-views.d.ts +86 -0
- package/dist/commands/tools/info/skill-views.d.ts.map +1 -0
- package/dist/commands/tools/info/skill-views.js +383 -0
- package/dist/commands/tools/install/index.d.ts.map +1 -1
- package/dist/commands/tools/install/index.js +3 -25
- package/dist/commands/tools/list/list-tools.d.ts +2 -0
- package/dist/commands/tools/list/list-tools.d.ts.map +1 -1
- package/dist/commands/tools/list/list-tools.js +23 -4
- package/dist/commands/tools/remove/index.d.ts.map +1 -1
- package/dist/commands/tools/remove/index.js +35 -42
- package/dist/commands/tools/remove/remove-tools.d.ts +3 -2
- package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
- package/dist/commands/tools/remove/remove-tools.js +14 -4
- package/dist/commands/tools/shared/auto-sync.d.ts +12 -1
- package/dist/commands/tools/shared/auto-sync.d.ts.map +1 -1
- package/dist/commands/tools/shared/auto-sync.js +20 -4
- package/dist/commands/tools/shared/format-pack-inventory.d.ts +12 -1
- package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
- package/dist/commands/tools/shared/format-pack-inventory.js +48 -6
- package/dist/commands/tools/shared/in-process-sync.d.ts +11 -0
- package/dist/commands/tools/shared/in-process-sync.d.ts.map +1 -0
- package/dist/commands/tools/shared/in-process-sync.js +79 -0
- package/dist/commands/tools/shared/pack-evidence.d.ts +78 -2
- package/dist/commands/tools/shared/pack-evidence.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-evidence.js +96 -2
- package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts +11 -0
- package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-lifecycle-outcome.js +24 -3
- package/dist/commands/tools/shared/pack-provider-evidence.d.ts +73 -0
- package/dist/commands/tools/shared/pack-provider-evidence.d.ts.map +1 -0
- package/dist/commands/tools/shared/pack-provider-evidence.js +161 -0
- package/dist/commands/tools/shared/provider-context.d.ts +88 -0
- package/dist/commands/tools/shared/provider-context.d.ts.map +1 -0
- package/dist/commands/tools/shared/provider-context.js +77 -0
- package/dist/commands/tools/shared/provider-reachability.d.ts +93 -0
- package/dist/commands/tools/shared/provider-reachability.d.ts.map +1 -0
- package/dist/commands/tools/shared/provider-reachability.js +358 -0
- package/dist/commands/tools/shared/sync-evidence.d.ts +57 -0
- package/dist/commands/tools/shared/sync-evidence.d.ts.map +1 -0
- package/dist/commands/tools/shared/sync-evidence.js +147 -0
- package/dist/commands/tools/update/index.d.ts +0 -4
- package/dist/commands/tools/update/index.d.ts.map +1 -1
- package/dist/commands/tools/update/index.js +22 -40
- package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
- package/dist/commands/tools/update/update-tools.js +23 -8
- package/dist/config/json.d.ts +36 -0
- package/dist/config/json.d.ts.map +1 -1
- package/dist/config/json.js +135 -3
- package/dist/config/oat-config.d.ts +41 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +111 -0
- package/dist/drift/index.d.ts +2 -0
- package/dist/drift/index.d.ts.map +1 -1
- package/dist/drift/index.js +1 -0
- package/dist/drift/skill-view-diagnostic.d.ts +197 -0
- package/dist/drift/skill-view-diagnostic.d.ts.map +1 -0
- package/dist/drift/skill-view-diagnostic.js +343 -0
- package/dist/validation/skills.d.ts +1 -0
- package/dist/validation/skills.d.ts.map +1 -1
- package/dist/validation/skills.js +181 -19
- package/package.json +2 -2
|
@@ -18,11 +18,31 @@ now })` is pure. Its result contains the effective `decision`, the
|
|
|
18
18
|
`resolutionSource`, whether a prompt is needed, an optional state `record`, and
|
|
19
19
|
warnings.
|
|
20
20
|
|
|
21
|
-
In autonomous mode, `projectRecap`
|
|
22
|
-
`autonomous_policy
|
|
23
|
-
and reported as a warning. Autonomous
|
|
24
|
-
only when the kickoff prompt
|
|
25
|
-
`skip` without writing an
|
|
21
|
+
In autonomous mode, `projectRecap` resolves to `generate` with source
|
|
22
|
+
`autonomous_policy` on any host whose recap seams resolve. A lower-precedence
|
|
23
|
+
skip or `never` preference is overridden and reported as a warning. Autonomous
|
|
24
|
+
`projectExplainer` resolves to `generate` only when the kickoff prompt
|
|
25
|
+
explicitly requested it; otherwise it resolves to `skip` without writing an
|
|
26
|
+
invalid prompt-source skip record.
|
|
27
|
+
|
|
28
|
+
Autonomous `projectRecap` resolution accepts an optional `seamProbe`, the
|
|
29
|
+
result of `scripts/probe-recap-seams.mjs#probeRecapSeams`. It is a pre-flight
|
|
30
|
+
capability input, never a post-hoc reaction to a failed run:
|
|
31
|
+
|
|
32
|
+
- `ok: true` leaves resolution unchanged, so a configured host still forces
|
|
33
|
+
`generate / autonomous_policy`.
|
|
34
|
+
- `seams-unavailable` — no callback and no module path is supplied for a seam
|
|
35
|
+
this mode requires — resolves `skip` with source `capability_probe` and a
|
|
36
|
+
warning naming the unavailable seams.
|
|
37
|
+
- `seams-invalid` — a seam is supplied but violates a resolution rule — throws
|
|
38
|
+
`E_RECAP_SEAMS_INVALID` and fails closed exactly as the runtime would. A
|
|
39
|
+
configured-but-invalid seam is a configuration error, never a capability
|
|
40
|
+
skip.
|
|
41
|
+
|
|
42
|
+
A seam probe is scoped to autonomous `projectRecap`; supplying one for
|
|
43
|
+
`projectExplainer` or for interactive resolution is an error. A recap that
|
|
44
|
+
fails after a passing probe stays `failed` and is never reinterpreted as a
|
|
45
|
+
skip.
|
|
26
46
|
|
|
27
47
|
In interactive mode, an existing valid project record prevents another prompt.
|
|
28
48
|
Preferences `always` and `never` resolve directly but are not copied into
|
|
@@ -47,12 +67,22 @@ oat_project_recap:
|
|
|
47
67
|
|
|
48
68
|
Allowed decision/source pairs are:
|
|
49
69
|
|
|
50
|
-
| Product | Allowed pairs
|
|
51
|
-
| ------------------ |
|
|
52
|
-
| `projectExplainer` | `generate/interactive`, `skip/interactive`, `generate/kickoff_prompt`
|
|
53
|
-
| `projectRecap` | `generate/interactive`, `skip/interactive`, `generate/autonomous_policy` |
|
|
70
|
+
| Product | Allowed pairs |
|
|
71
|
+
| ------------------ | ------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `projectExplainer` | `generate/interactive`, `skip/interactive`, `generate/kickoff_prompt` |
|
|
73
|
+
| `projectRecap` | `generate/interactive`, `skip/interactive`, `generate/autonomous_policy`, `skip/capability_probe` |
|
|
54
74
|
|
|
55
|
-
In particular, `skip/autonomous_policy` is invalid
|
|
75
|
+
In particular, `skip/autonomous_policy` is invalid: an autonomous skip is
|
|
76
|
+
recordable only as the probe-driven `skip/capability_probe`, and that pair is
|
|
77
|
+
product-scoped to `projectRecap`. `generate/capability_probe` is invalid too,
|
|
78
|
+
because a probe can only withhold a run, never authorize one.
|
|
79
|
+
|
|
80
|
+
The pair is product-scoped but not mode-scoped, and that is deliberate. Only
|
|
81
|
+
autonomous resolution can _produce_ a `skip/capability_probe`, but once one is
|
|
82
|
+
persisted a later interactive resolution reads it back as a valid recorded
|
|
83
|
+
decision and honors it, exactly as it honors `skip/interactive`. A resumed
|
|
84
|
+
project therefore neither re-prompts nor fails validation on a legitimately
|
|
85
|
+
recorded capability skip.
|
|
56
86
|
|
|
57
87
|
## Safe persistence
|
|
58
88
|
|
|
@@ -120,6 +150,23 @@ the relative path and public URL. Neither summary contract is mutated in place.
|
|
|
120
150
|
|
|
121
151
|
## Browser and visual-review execution
|
|
122
152
|
|
|
153
|
+
Lifecycle callers probe seam availability before attempting an unattended
|
|
154
|
+
`project-recap`. `scripts/probe-recap-seams.mjs#probeRecapSeams({ mode, ...
|
|
155
|
+
seams })` is pure: it applies the same resolution and exclusivity rules this
|
|
156
|
+
adapter applies at run time, but never imports a module, calls a callback,
|
|
157
|
+
validates a session brand, or touches the filesystem. Unattended runs require
|
|
158
|
+
all five seams — author, fact critic, browser session, visual critic, and set
|
|
159
|
+
planner — and interactive runs require the author and fact critic. A probe that
|
|
160
|
+
checks only four of the five passes on a host with no set planner and the
|
|
161
|
+
adapter still throws `E_SET_PLANNER_REQUIRED`, so the planner is probed with
|
|
162
|
+
the rest.
|
|
163
|
+
|
|
164
|
+
The probe classifies each seam as resolved, `missing`, or `invalid` and never
|
|
165
|
+
conflates them. Only `missing` may become a recorded skip. Requirements below
|
|
166
|
+
are unchanged for every run that is actually attempted: the probe gates whether
|
|
167
|
+
a run is attempted, and never weakens the evidence a run that happens must
|
|
168
|
+
produce.
|
|
169
|
+
|
|
123
170
|
Every unattended `project-recap` must provide exactly one browser-evidence
|
|
124
171
|
session and exactly one whole-set visual critic through the adapter's
|
|
125
172
|
first-class boundary. In-process callers pass `browserSession`, created by the
|
|
@@ -8,9 +8,25 @@ const TERMINAL_OUTCOMES = new Set([
|
|
|
8
8
|
'failed',
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* A skip reason is the recorded `source` of the skip decision, so the guard
|
|
13
|
+
* accepts exactly the sources that may produce a `skip` recap record.
|
|
14
|
+
*/
|
|
15
|
+
const SKIP_REASONS = new Set(['interactive', 'capability_probe']);
|
|
16
|
+
|
|
17
|
+
export function checkTerminalOutcome({ intent, outcome, reason }) {
|
|
18
|
+
if (reason !== undefined && !SKIP_REASONS.has(reason)) {
|
|
19
|
+
throw recapOutcomeError(
|
|
20
|
+
`Recap skip reason must be one of ${[...SKIP_REASONS].join(', ')}.`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
12
23
|
if (intent === 'skip') {
|
|
13
|
-
return { ok: true, intent, outcome: null };
|
|
24
|
+
return { ok: true, intent, outcome: null, reason: reason ?? null };
|
|
25
|
+
}
|
|
26
|
+
if (reason !== undefined) {
|
|
27
|
+
throw recapOutcomeError(
|
|
28
|
+
'A recap skip reason applies only to a skip intent.',
|
|
29
|
+
);
|
|
14
30
|
}
|
|
15
31
|
if (intent !== 'generate') {
|
|
16
32
|
throw recapOutcomeError('Recap intent must be generate or skip.');
|
|
@@ -24,7 +40,7 @@ export function checkTerminalOutcome({ intent, outcome }) {
|
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
async function main(argv) {
|
|
27
|
-
const { intent, manifestPath } = parseArguments(argv);
|
|
43
|
+
const { intent, manifestPath, reason } = parseArguments(argv);
|
|
28
44
|
let outcome;
|
|
29
45
|
if (manifestPath !== undefined) {
|
|
30
46
|
let manifest;
|
|
@@ -37,12 +53,17 @@ async function main(argv) {
|
|
|
37
53
|
}
|
|
38
54
|
outcome = manifest?.outcome;
|
|
39
55
|
}
|
|
40
|
-
return checkTerminalOutcome({
|
|
56
|
+
return checkTerminalOutcome({
|
|
57
|
+
intent,
|
|
58
|
+
outcome,
|
|
59
|
+
...(reason !== undefined && { reason }),
|
|
60
|
+
});
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
function parseArguments(argv) {
|
|
44
64
|
let intent;
|
|
45
65
|
let manifestPath;
|
|
66
|
+
let reason;
|
|
46
67
|
for (let index = 0; index < argv.length; index += 2) {
|
|
47
68
|
const flag = argv[index];
|
|
48
69
|
const value = argv[index + 1];
|
|
@@ -53,11 +74,13 @@ function parseArguments(argv) {
|
|
|
53
74
|
intent = value;
|
|
54
75
|
} else if (flag === '--manifest') {
|
|
55
76
|
manifestPath = value;
|
|
77
|
+
} else if (flag === '--skip-reason') {
|
|
78
|
+
reason = value;
|
|
56
79
|
} else {
|
|
57
80
|
throw recapOutcomeError(`Unsupported argument: ${flag}.`);
|
|
58
81
|
}
|
|
59
82
|
}
|
|
60
|
-
return { intent, manifestPath };
|
|
83
|
+
return { intent, manifestPath, reason };
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
function recapOutcomeError(message) {
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight capability probe for the provider-neutral seams a lifecycle
|
|
3
|
+
* `project-recap` run requires.
|
|
4
|
+
*
|
|
5
|
+
* The probe is pure: it inspects only the inputs a caller would hand to
|
|
6
|
+
* `runOatExplainer`, applying the same resolution and exclusivity rules
|
|
7
|
+
* `scripts/run.mjs` applies at runtime. It never imports a module, calls a
|
|
8
|
+
* callback, validates a browser-session brand, or touches the filesystem.
|
|
9
|
+
*
|
|
10
|
+
* Three outcomes are kept distinct and are never conflated:
|
|
11
|
+
*
|
|
12
|
+
* - `missing` — no provider is supplied for a seam this mode requires. This is
|
|
13
|
+
* the only outcome that may become a recordable `skip` / `capability_probe`
|
|
14
|
+
* intent (`code: 'seams-unavailable'`).
|
|
15
|
+
* - `invalid` — a seam is supplied but violates a resolution rule (both a
|
|
16
|
+
* callback and a module path, a non-function callback, an empty module path,
|
|
17
|
+
* or an unsupported `coreOptions` route). It fails closed with the same
|
|
18
|
+
* message the runtime would raise (`code: 'seams-invalid'`) and is never a
|
|
19
|
+
* skip.
|
|
20
|
+
* - runtime failure — an unloadable module, a non-function export, an unbranded
|
|
21
|
+
* browser session, or a failed generation. The probe never observes these
|
|
22
|
+
* because it never loads or calls anything, so a failed run after a passing
|
|
23
|
+
* probe stays `failed`.
|
|
24
|
+
*
|
|
25
|
+
* Each seam has its own classifier that mirrors its `run.mjs` resolver check by
|
|
26
|
+
* check and in the same order, because the resolvers do not share one order:
|
|
27
|
+
* `resolveLifecycleCritic` counts only function/truthy candidates before it
|
|
28
|
+
* type-checks, while the author, visual critic, and set planner type-check the
|
|
29
|
+
* callback before they reject a direct-plus-module conflict. Keep each
|
|
30
|
+
* classifier aligned with the resolver named in its comment.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const MODES = new Set(['interactive', 'unattended']);
|
|
34
|
+
|
|
35
|
+
const isSet = (value) => value !== undefined;
|
|
36
|
+
|
|
37
|
+
const invalid = (reason, message) => ({ status: 'invalid', reason, message });
|
|
38
|
+
const supplied = () => ({ status: 'supplied' });
|
|
39
|
+
const absent = () => ({ status: 'absent' });
|
|
40
|
+
|
|
41
|
+
function badModulePath(value) {
|
|
42
|
+
return typeof value !== 'string' || value.trim().length === 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Mirrors `run.mjs#resolveLifecycleAuthor`. */
|
|
46
|
+
function classifyAuthor({ author, authorModulePath }, coreOptions) {
|
|
47
|
+
if (isSet(coreOptions.author)) {
|
|
48
|
+
return invalid(
|
|
49
|
+
'unsupported-core-option',
|
|
50
|
+
'coreOptions.author is not supported at the OAT adapter boundary; supply author directly.',
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (isSet(author) && typeof author !== 'function') {
|
|
54
|
+
return invalid(
|
|
55
|
+
'callback-not-a-function',
|
|
56
|
+
'author must be a function when supplied.',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (isSet(author) && isSet(authorModulePath)) {
|
|
60
|
+
return invalid(
|
|
61
|
+
'multiple-sources',
|
|
62
|
+
'Supply only one provider-neutral author callback or author module entry point.',
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (!isSet(author) && !isSet(authorModulePath)) return absent();
|
|
66
|
+
if (isSet(authorModulePath) && badModulePath(authorModulePath)) {
|
|
67
|
+
return invalid(
|
|
68
|
+
'empty-module-path',
|
|
69
|
+
'authorModulePath must be a non-empty path.',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return supplied();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Mirrors `run.mjs#resolveLifecycleCritic`.
|
|
77
|
+
*
|
|
78
|
+
* `resolveLifecycleCritic` returns `null` rather than throwing when no critic is
|
|
79
|
+
* supplied, so treating the critic as required looks stricter than the adapter
|
|
80
|
+
* resolver in isolation. It is not, for the runs this probe serves: an
|
|
81
|
+
* artifact-bound lifecycle closeout recap passes no `suppliedFactBasePath`, so
|
|
82
|
+
* `bindProjectSources` binds it `factBase.mode: 'federated'`
|
|
83
|
+
* (`bind-project-sources.mjs`), and the core throws for a federated fact base
|
|
84
|
+
* with no critic (`explainer-kit/scripts/lib/fact-base.mjs`). Such a host cannot
|
|
85
|
+
* produce a recap today; probing the critic turns that mid-run core failure into
|
|
86
|
+
* a pre-flight capability skip. (A `supplied` fact base is a different
|
|
87
|
+
* invocation path and does skip the core's critic check, but no lifecycle recap
|
|
88
|
+
* caller uses it.) Do not "fix" this to match `resolveLifecycleCritic` — that
|
|
89
|
+
* would reintroduce an unrunnable recap.
|
|
90
|
+
*/
|
|
91
|
+
function classifyCritic({ critic, criticModulePath }, coreOptions) {
|
|
92
|
+
// The runtime counts only candidates that already look usable, so a
|
|
93
|
+
// non-function callback beside a module path is a type error rather than a
|
|
94
|
+
// conflict.
|
|
95
|
+
const candidates = [
|
|
96
|
+
typeof critic === 'function' ? critic : null,
|
|
97
|
+
typeof coreOptions.critic === 'function' ? coreOptions.critic : null,
|
|
98
|
+
criticModulePath ? criticModulePath : null,
|
|
99
|
+
].filter(Boolean);
|
|
100
|
+
if (candidates.length > 1) {
|
|
101
|
+
return invalid(
|
|
102
|
+
'multiple-sources',
|
|
103
|
+
'Supply only one provider-neutral critic callback or critic module entry point.',
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (isSet(critic) && typeof critic !== 'function') {
|
|
107
|
+
return invalid(
|
|
108
|
+
'callback-not-a-function',
|
|
109
|
+
'critic must be a function when supplied.',
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (isSet(coreOptions.critic) && typeof coreOptions.critic !== 'function') {
|
|
113
|
+
return invalid(
|
|
114
|
+
'callback-not-a-function',
|
|
115
|
+
'coreOptions.critic must be a function when supplied.',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (isSet(criticModulePath)) {
|
|
119
|
+
return badModulePath(criticModulePath)
|
|
120
|
+
? invalid(
|
|
121
|
+
'empty-module-path',
|
|
122
|
+
'criticModulePath must be a non-empty path.',
|
|
123
|
+
)
|
|
124
|
+
: supplied();
|
|
125
|
+
}
|
|
126
|
+
// `coreOptions.critic` is the documented compatibility route, so it satisfies
|
|
127
|
+
// the seam rather than invalidating it.
|
|
128
|
+
return (critic ?? coreOptions.critic) ? supplied() : absent();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Mirrors `run.mjs#resolveLifecycleBrowserSession`. */
|
|
132
|
+
function classifyBrowserSession(
|
|
133
|
+
{
|
|
134
|
+
browserSession,
|
|
135
|
+
browserSessionModulePath,
|
|
136
|
+
browserProbe,
|
|
137
|
+
browserProbeModulePath,
|
|
138
|
+
},
|
|
139
|
+
coreOptions,
|
|
140
|
+
) {
|
|
141
|
+
if (isSet(coreOptions.browserProbe)) {
|
|
142
|
+
return invalid(
|
|
143
|
+
'unsupported-core-option',
|
|
144
|
+
'coreOptions.browserProbe is not supported at the OAT adapter boundary; supply browserSession directly.',
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (isSet(browserProbe) || isSet(browserProbeModulePath)) {
|
|
148
|
+
return invalid(
|
|
149
|
+
'unsupported-input',
|
|
150
|
+
'Bare browserProbe callbacks are not supported at the OAT adapter boundary; supply a branded browserSession created by createBrowserProbeSession().',
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (isSet(coreOptions.browserSession)) {
|
|
154
|
+
return invalid(
|
|
155
|
+
'unsupported-core-option',
|
|
156
|
+
'coreOptions.browserSession is not supported at the OAT adapter boundary; supply browserSession directly.',
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (isSet(browserSession) && isSet(browserSessionModulePath)) {
|
|
160
|
+
return invalid(
|
|
161
|
+
'multiple-sources',
|
|
162
|
+
'Supply only one browser session descriptor or browser session module entry point.',
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (isSet(browserSessionModulePath)) {
|
|
166
|
+
return badModulePath(browserSessionModulePath)
|
|
167
|
+
? invalid(
|
|
168
|
+
'empty-module-path',
|
|
169
|
+
'browserSessionModulePath must be a non-empty path.',
|
|
170
|
+
)
|
|
171
|
+
: supplied();
|
|
172
|
+
}
|
|
173
|
+
// A browser session is a descriptor the runtime resolves by truthiness, so a
|
|
174
|
+
// falsy one raises the same "requires exactly one" error as supplying none.
|
|
175
|
+
// Classifying it invalid would fail the unattended completion this probe
|
|
176
|
+
// exists to unblock.
|
|
177
|
+
return browserSession ? supplied() : absent();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Mirrors `run.mjs#resolveLifecycleVisualCritic` and `resolveProviderCallback`. */
|
|
181
|
+
function classifyVisualCritic(
|
|
182
|
+
{ visualCritic, visualCriticModulePath },
|
|
183
|
+
coreOptions,
|
|
184
|
+
) {
|
|
185
|
+
if (isSet(coreOptions.visualCritic)) {
|
|
186
|
+
return invalid(
|
|
187
|
+
'unsupported-core-option',
|
|
188
|
+
'coreOptions.visualCritic is not supported at the OAT adapter boundary; supply visualCritic directly.',
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (isSet(visualCritic) && typeof visualCritic !== 'function') {
|
|
192
|
+
return invalid(
|
|
193
|
+
'callback-not-a-function',
|
|
194
|
+
'visualCritic must be a function when supplied.',
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (isSet(visualCritic) && isSet(visualCriticModulePath)) {
|
|
198
|
+
return invalid(
|
|
199
|
+
'multiple-sources',
|
|
200
|
+
'Supply only one provider-neutral visual critic callback or visual critic module entry point.',
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (!isSet(visualCriticModulePath)) {
|
|
204
|
+
return visualCritic ? supplied() : absent();
|
|
205
|
+
}
|
|
206
|
+
return badModulePath(visualCriticModulePath)
|
|
207
|
+
? invalid(
|
|
208
|
+
'empty-module-path',
|
|
209
|
+
'visualCriticModulePath must be a non-empty path.',
|
|
210
|
+
)
|
|
211
|
+
: supplied();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Mirrors `run.mjs#resolveLifecycleSetPlanner`. */
|
|
215
|
+
function classifyPlanSet({ planSet, planSetModulePath }, coreOptions) {
|
|
216
|
+
if (isSet(coreOptions.planSet)) {
|
|
217
|
+
return invalid(
|
|
218
|
+
'unsupported-core-option',
|
|
219
|
+
'coreOptions.planSet is not supported at the OAT adapter boundary; supply planSet directly.',
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (isSet(planSet) && typeof planSet !== 'function') {
|
|
223
|
+
return invalid(
|
|
224
|
+
'callback-not-a-function',
|
|
225
|
+
'planSet must be a function when supplied.',
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (isSet(planSet) && isSet(planSetModulePath)) {
|
|
229
|
+
return invalid(
|
|
230
|
+
'multiple-sources',
|
|
231
|
+
'Supply only one provider-neutral set planner callback or set planner module entry point.',
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (!isSet(planSet) && !isSet(planSetModulePath)) return absent();
|
|
235
|
+
if (isSet(planSetModulePath) && badModulePath(planSetModulePath)) {
|
|
236
|
+
return invalid(
|
|
237
|
+
'empty-module-path',
|
|
238
|
+
'planSetModulePath must be a non-empty path.',
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return supplied();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The five seams an unattended `project-recap` requires, in report order.
|
|
246
|
+
*
|
|
247
|
+
* `requiredIn` mirrors the adapter contract: the author and the fact critic are
|
|
248
|
+
* required by every federated adapter run, while the browser session, visual
|
|
249
|
+
* critic, and set planner are required only for unattended project recaps.
|
|
250
|
+
*/
|
|
251
|
+
const SEAMS = [
|
|
252
|
+
{
|
|
253
|
+
id: 'author',
|
|
254
|
+
requiredIn: ['interactive', 'unattended'],
|
|
255
|
+
classify: classifyAuthor,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: 'critic',
|
|
259
|
+
requiredIn: ['interactive', 'unattended'],
|
|
260
|
+
classify: classifyCritic,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: 'browserSession',
|
|
264
|
+
requiredIn: ['unattended'],
|
|
265
|
+
classify: classifyBrowserSession,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: 'visualCritic',
|
|
269
|
+
requiredIn: ['unattended'],
|
|
270
|
+
classify: classifyVisualCritic,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'planSet',
|
|
274
|
+
requiredIn: ['unattended'],
|
|
275
|
+
classify: classifyPlanSet,
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
export const RECAP_SEAM_IDS = Object.freeze(SEAMS.map((seam) => seam.id));
|
|
280
|
+
export const RECAP_PROBE_CODES = Object.freeze([
|
|
281
|
+
'seams-ok',
|
|
282
|
+
'seams-unavailable',
|
|
283
|
+
'seams-invalid',
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
export function probeRecapSeams(inputs = {}) {
|
|
287
|
+
if (!inputs || typeof inputs !== 'object' || Array.isArray(inputs)) {
|
|
288
|
+
throw new TypeError('Seam probe inputs must be an object.');
|
|
289
|
+
}
|
|
290
|
+
const { mode = 'unattended', coreOptions = {} } = inputs;
|
|
291
|
+
if (!MODES.has(mode)) {
|
|
292
|
+
throw new Error(`Unsupported lifecycle mode: ${String(mode)}`);
|
|
293
|
+
}
|
|
294
|
+
if (!coreOptions || typeof coreOptions !== 'object') {
|
|
295
|
+
throw new TypeError('coreOptions must be an object when supplied.');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const missing = [];
|
|
299
|
+
const invalidSeams = [];
|
|
300
|
+
const resolved = [];
|
|
301
|
+
|
|
302
|
+
for (const seam of SEAMS) {
|
|
303
|
+
const outcome = seam.classify(inputs, coreOptions);
|
|
304
|
+
if (outcome.status === 'invalid') {
|
|
305
|
+
invalidSeams.push({
|
|
306
|
+
seam: seam.id,
|
|
307
|
+
reason: outcome.reason,
|
|
308
|
+
message: outcome.message,
|
|
309
|
+
});
|
|
310
|
+
} else if (outcome.status === 'supplied') {
|
|
311
|
+
resolved.push(seam.id);
|
|
312
|
+
} else if (seam.requiredIn.includes(mode)) {
|
|
313
|
+
missing.push(seam.id);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Invalid outranks missing: a broken configuration must fail closed rather
|
|
318
|
+
// than degrade into a capability skip.
|
|
319
|
+
if (invalidSeams.length > 0) {
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
mode,
|
|
323
|
+
code: 'seams-invalid',
|
|
324
|
+
missing,
|
|
325
|
+
invalid: invalidSeams,
|
|
326
|
+
resolved,
|
|
327
|
+
message: `Recap seam configuration is invalid: ${invalidSeams
|
|
328
|
+
.map((entry) => entry.message)
|
|
329
|
+
.join(' ')}`,
|
|
330
|
+
guidance:
|
|
331
|
+
'Correct the reported seam configuration. A configured-but-invalid seam fails closed and is never a capability skip.',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (missing.length > 0) {
|
|
336
|
+
return {
|
|
337
|
+
ok: false,
|
|
338
|
+
mode,
|
|
339
|
+
code: 'seams-unavailable',
|
|
340
|
+
missing,
|
|
341
|
+
invalid: invalidSeams,
|
|
342
|
+
resolved,
|
|
343
|
+
message: `No provider is configured for ${formatList(missing)}, so ${article(mode)} ${mode} project recap cannot run on this host.`,
|
|
344
|
+
guidance:
|
|
345
|
+
mode === 'unattended'
|
|
346
|
+
? `Configure ${formatList(missing)} to generate an unattended project recap, or let autonomous resolution record the recap as skip / capability_probe.`
|
|
347
|
+
: `Configure ${formatList(missing)} to generate an interactive project recap, or record an interactive skip decision.`,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
ok: true,
|
|
353
|
+
mode,
|
|
354
|
+
code: 'seams-ok',
|
|
355
|
+
missing,
|
|
356
|
+
invalid: invalidSeams,
|
|
357
|
+
resolved,
|
|
358
|
+
message: `Every ${mode} project recap seam resolves on this host.`,
|
|
359
|
+
guidance: null,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** `unattended` and `interactive` both take "an"; keep the surface grammatical. */
|
|
364
|
+
function article(mode) {
|
|
365
|
+
return /^[aeiou]/i.test(mode) ? 'an' : 'a';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function formatList(values) {
|
|
369
|
+
if (values.length === 1) return values[0];
|
|
370
|
+
return `${values.slice(0, -1).join(', ')} and ${values.at(-1)}`;
|
|
371
|
+
}
|