@open-agent-toolkit/cli 0.2.29 → 0.2.31
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/configuration.md +52 -1
- package/assets/docs/contributing/code.md +19 -7
- package/assets/docs/contributing/explainer-kit-verification.md +9 -1
- package/assets/docs/contributing/skills.md +9 -0
- package/assets/docs/workflows/projects/artifacts.md +5 -0
- package/assets/docs/workflows/projects/index.md +1 -0
- package/assets/docs/workflows/projects/lifecycle.md +23 -0
- package/assets/docs/workflows/projects/retro.md +261 -0
- package/assets/docs/workflows/skills/explainer-kit-providers.md +16 -5
- package/assets/docs/workflows/skills/explainer-kit.md +98 -38
- package/assets/docs/workflows/skills/index.md +6 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +33 -8
- package/assets/skills/explainer-kit/briefs/project-recap.md +25 -7
- package/assets/skills/explainer-kit/recipes/project-recap.v2.json +72 -0
- package/assets/skills/explainer-kit/references/contracts.md +49 -17
- package/assets/skills/explainer-kit/references/destination-contract.md +141 -25
- package/assets/skills/explainer-kit/references/extension-contract.md +19 -10
- package/assets/skills/explainer-kit/references/visual-authoring.md +24 -0
- package/assets/skills/explainer-kit/references/visual-review.md +19 -5
- package/assets/skills/explainer-kit/schemas/author-request.v3.schema.json +241 -0
- package/assets/skills/explainer-kit/schemas/publish-receipt.v2.schema.json +215 -0
- package/assets/skills/explainer-kit/schemas/publish-request.v2.schema.json +34 -0
- package/assets/skills/explainer-kit/schemas/run-request.schema.json +4 -1
- package/assets/skills/explainer-kit/schemas/terminal-evidence.v1.schema.json +81 -0
- package/assets/skills/explainer-kit/schemas/visual-review-evidence.v1.schema.json +66 -0
- package/assets/skills/explainer-kit/scripts/lib/catalog.mjs +109 -3
- package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +550 -17
- package/assets/skills/explainer-kit/scripts/lib/durability.mjs +90 -8
- package/assets/skills/explainer-kit/scripts/lib/fs-safe.mjs +5 -0
- package/assets/skills/explainer-kit/scripts/lib/internal-references.mjs +538 -0
- package/assets/skills/explainer-kit/scripts/lib/package-coverage.mjs +129 -11
- package/assets/skills/explainer-kit/scripts/lib/publication-policy.mjs +54 -0
- package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +2 -1
- package/assets/skills/explainer-kit/scripts/lib/records.mjs +139 -22
- package/assets/skills/explainer-kit/scripts/lib/s3-roots.mjs +353 -0
- package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +237 -107
- package/assets/skills/explainer-kit/scripts/lib/set-plan.mjs +1 -0
- package/assets/skills/explainer-kit/scripts/lib/terminal-evidence.mjs +157 -0
- package/assets/skills/explainer-kit/scripts/lib/visual-review.mjs +26 -6
- package/assets/skills/explainer-kit/scripts/run.mjs +1006 -144
- package/assets/skills/oat-explainer-kit/SKILL.md +16 -3
- package/assets/skills/oat-explainer-kit/references/config-contract.md +13 -8
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +50 -6
- package/assets/skills/oat-explainer-kit/references/migration.md +2 -1
- package/assets/skills/oat-explainer-kit/references/visual-review-callback.md +11 -0
- package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +37 -15
- package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +83 -0
- package/assets/skills/oat-explainer-kit/scripts/derive-destination.mjs +91 -0
- package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +66 -10
- package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +60 -21
- package/assets/skills/oat-explainer-kit/scripts/resolve-paths.mjs +52 -8
- package/assets/skills/oat-explainer-kit/scripts/run.mjs +271 -36
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +38 -38
- package/assets/skills/oat-project-complete/SKILL.md +60 -8
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +38 -38
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +28 -6
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +38 -38
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +38 -38
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +38 -38
- package/assets/skills/oat-project-retro/SKILL.md +310 -0
- package/assets/skills/oat-project-retro/references/apply-procedure.md +212 -0
- package/assets/skills/oat-project-retro/references/evidence-and-lanes.md +91 -0
- package/assets/skills/oat-project-retro/references/retro-quality-bar.md +100 -0
- package/assets/skills/oat-project-retro-file/SKILL.md +387 -0
- package/assets/templates/project-retro.md +236 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +99 -0
- package/dist/commands/init/tools/shared/skill-manifest.d.ts +2 -2
- package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
- package/dist/commands/init/tools/shared/skill-manifest.js +3 -0
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +57 -7
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts +29 -0
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts.map +1 -0
- package/dist/commands/project/archive/explainer-terminal-evidence.js +37 -0
- package/dist/config/oat-config.d.ts +14 -1
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +42 -2
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +9 -0
- package/package.json +2 -2
- /package/assets/skills/explainer-kit/recipes/{project-recap.json → project-recap.v1.json} +0 -0
- /package/assets/skills/explainer-kit/schemas/{publish-receipt.schema.json → publish-receipt.v1.schema.json} +0 -0
- /package/assets/skills/explainer-kit/schemas/{publish-request.schema.json → publish-request.v1.schema.json} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oat-explainer-kit
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.6
|
|
4
4
|
description: Use when building project explainers or recaps from OAT configuration, state, and lifecycle artifacts.
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
user-invocable: true
|
|
@@ -37,7 +37,7 @@ rollback, and operator-owned real-wrapper gate, use `references/migration.md`.
|
|
|
37
37
|
|
|
38
38
|
Before reading OAT config or invoking the core, call
|
|
39
39
|
`scripts/check-core.mjs#checkCoreCompatibility` with this installed skill
|
|
40
|
-
directory and minimum core version `2.0
|
|
40
|
+
directory and minimum core version `2.1.0`. Continue only when it returns
|
|
41
41
|
`ok: true`.
|
|
42
42
|
|
|
43
43
|
- Missing core: stop and show
|
|
@@ -58,7 +58,7 @@ Call `scripts/run.mjs#runOatExplainer` with the repository root, project
|
|
|
58
58
|
invocation, active project path, recipe, slug, lifecycle mode, and any explicit
|
|
59
59
|
runtime overrides. The adapter:
|
|
60
60
|
|
|
61
|
-
1. checks the user-scoped installed core at minimum version `2.0
|
|
61
|
+
1. checks the user-scoped installed core at minimum version `2.1.0`;
|
|
62
62
|
2. resolves only the public `explainers.*` and `workflow.explainers.*` keys;
|
|
63
63
|
3. derives the canonical project output root;
|
|
64
64
|
4. binds approved OAT artifacts to the recipe's single `project` source set;
|
|
@@ -72,6 +72,13 @@ Missing optional artifacts are omitted, but at least one approved lifecycle
|
|
|
72
72
|
artifact is required. An explicit supplied fact-base path bypasses artifact
|
|
73
73
|
federation and is passed through as `factBase.mode: supplied`.
|
|
74
74
|
|
|
75
|
+
New project-recap requests select immutable `project-recap@2`: one
|
|
76
|
+
navigational hub is the floor, while a diagram, walkthrough deck, or deep dive
|
|
77
|
+
is selected only when the planner can state its distinct reader question,
|
|
78
|
+
source evidence, and medium rationale. The installed core continues reading
|
|
79
|
+
`project-recap@1` for replay; the adapter never rewrites a retained request's
|
|
80
|
+
recipe selector.
|
|
81
|
+
|
|
75
82
|
Before invocation, read `references/author-callback.md`. Unattended
|
|
76
83
|
`project-recap` runs require exactly one provider-neutral set planner:
|
|
77
84
|
in-process callers supply `planSet`, while JSON/CLI callers supply
|
|
@@ -99,6 +106,12 @@ matching exports. Bare browser callbacks and caller-authored runtime metadata
|
|
|
99
106
|
are rejected. These are first-class adapter inputs; do not place either
|
|
100
107
|
provider in `coreOptions`.
|
|
101
108
|
|
|
109
|
+
The critic judges typography, hierarchy, composition, density, medium leverage,
|
|
110
|
+
template repetition, diagram semantics, and cross-artifact cohesion from the
|
|
111
|
+
bound rendered evidence. Keep those criteria in prose. Preserve the core's
|
|
112
|
+
existing `visual-review-result/v1` contract and actionable `pass`/`correct`
|
|
113
|
+
behavior; do not add adapter-owned scores, layout thresholds, or result fields.
|
|
114
|
+
|
|
102
115
|
The adapter resolves all executable providers before core invocation, enforces
|
|
103
116
|
direct-versus-module mutual exclusion, and requires distinct identities for
|
|
104
117
|
authoring, fact criticism, browser evidence, and visual criticism. A missing,
|
|
@@ -17,11 +17,12 @@ constructing an `explainer-kit.run-request/v1`.
|
|
|
17
17
|
| `explainers.publish.s3Uri` | shared | unset |
|
|
18
18
|
| `explainers.publish.publicBaseUrl` | shared | unset |
|
|
19
19
|
| `explainers.publish.awsRegion` | shared | unset |
|
|
20
|
+
| `explainers.publish.publicAccess` | shared | `public` |
|
|
20
21
|
| `explainers.publish.awsProfile` | local, user | unset |
|
|
21
22
|
| `workflow.explainers.projectExplainer` | local, shared, user | `ask` |
|
|
22
23
|
| `workflow.explainers.projectRecap` | local, shared, user | `ask` |
|
|
23
24
|
|
|
24
|
-
Explicit runtime inputs may override these
|
|
25
|
+
Explicit runtime inputs may override these twelve keys for one invocation. They
|
|
25
26
|
do not write config. Recipe, slug, fact-base path, output root, per-run art
|
|
26
27
|
direction, and private wrapper lanes are invocation inputs rather than config
|
|
27
28
|
keys and are rejected from the runtime config-override map.
|
|
@@ -44,12 +45,16 @@ warning. The adapter passes a resolved bundle as `theme.suppliedBundlePath`.
|
|
|
44
45
|
|
|
45
46
|
## Publish block
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
`provider`, `s3Uri`, `publicBaseUrl`, and `awsRegion` form one complete publish
|
|
49
|
+
block. If any field is absent, the adapter returns a structured build-only
|
|
50
|
+
report listing every missing field and does not construct publish config.
|
|
51
|
+
`awsProfile` is optional and uses the normal AWS credential chain when absent.
|
|
52
|
+
`publicAccess` accepts `public` or `protected` and defaults to `public`;
|
|
53
|
+
source metadata is retained and the resolved mode is carried explicitly in the
|
|
54
|
+
versioned core publish request. Destination roots are normalized without trailing
|
|
55
|
+
slashes. Lifecycle callers must explicitly select publish durability; complete
|
|
56
|
+
configuration only makes publication available and never authorizes it. The
|
|
57
|
+
existing human gate remains mandatory.
|
|
53
58
|
|
|
54
59
|
## Canonical output roots
|
|
55
60
|
|
|
@@ -68,5 +73,5 @@ core creates output.
|
|
|
68
73
|
The adapter emits `explainer-kit.run-request/v1` with the requested recipe,
|
|
69
74
|
slug, fact-base binding, mode, derived output root, resolved theme selection,
|
|
70
75
|
privacy choice, and explicit durability strategy. Publish durability adds a
|
|
71
|
-
complete `explainer-kit.publish-request/
|
|
76
|
+
complete `explainer-kit.publish-request/v2` whose `siteRoot` and
|
|
72
77
|
`manifestPath` point inside `<outputRoot>/<slug>/`.
|
|
@@ -86,6 +86,38 @@ The resolved callback is passed only as the `author` option to
|
|
|
86
86
|
`run-request.json`, or another retained data contract. Interactive runs use the
|
|
87
87
|
same author contract and differ only at the later approval gate.
|
|
88
88
|
|
|
89
|
+
## Repository invocation sources
|
|
90
|
+
|
|
91
|
+
A repository invocation must provide `suppliedFactBasePath`. The adapter binds
|
|
92
|
+
that validated fact-base input with provenance from the exact reviewed
|
|
93
|
+
repository revision returned by `resolveReviewedRepository`; it never resolves
|
|
94
|
+
or reads an active project for this invocation. Missing supplied input fails
|
|
95
|
+
closed before core invocation with an actionable requirement.
|
|
96
|
+
|
|
97
|
+
Repository runs write beneath `.oat/repo/reference/explainers/<run-slug>/` and
|
|
98
|
+
use repository-level publish roots unchanged. Project invocations retain their
|
|
99
|
+
approved lifecycle-artifact binding and project-local output behavior.
|
|
100
|
+
|
|
101
|
+
When publish durability is explicitly selected, the adapter constructs the
|
|
102
|
+
core `explainer-kit.publish-request/v2` with the per-invocation derived `s3Uri`,
|
|
103
|
+
`publicBaseUrl`, and source-aware `publicAccess`. New adapter runs emit this v2
|
|
104
|
+
shape atomically; the request carries no project/repository topology fields and
|
|
105
|
+
no credential material. The human-gated publisher returns a complete
|
|
106
|
+
`explainer-kit.publish-receipt/v2`, which the core validates against the
|
|
107
|
+
finalized manifest and generated catalog before the adapter forwards the
|
|
108
|
+
publication summary without destination reinterpretation. The immutable
|
|
109
|
+
`publish-request/v1` and `publish-receipt/v1` contracts remain supported only
|
|
110
|
+
for replay; v2 does not mutate either v1 contract in place. Configuration never
|
|
111
|
+
bypasses the human publication gate.
|
|
112
|
+
|
|
113
|
+
The adapter forwards the immutable `explainer-kit.publish-summary/v2` lifecycle
|
|
114
|
+
handoff without destination reinterpretation. Every v2 artifact entry retains
|
|
115
|
+
its source identity, rendered path, S3 URI, canonical public URL, content hash,
|
|
116
|
+
object verification, and public verification evidence. A
|
|
117
|
+
`publish-receipt/v1` replay instead yields the reduced immutable
|
|
118
|
+
`explainer-kit.publish-summary/v1` shape, whose artifact entries retain only
|
|
119
|
+
the relative path and public URL. Neither summary contract is mutated in place.
|
|
120
|
+
|
|
89
121
|
## Browser and visual-review execution
|
|
90
122
|
|
|
91
123
|
Every unattended `project-recap` must provide exactly one browser-evidence
|
|
@@ -109,10 +141,13 @@ request/result, and byte-bound whole-set review contracts.
|
|
|
109
141
|
|
|
110
142
|
The core retains canonical 320, 768, and 1440 viewport screenshots, paired
|
|
111
143
|
`explainer-kit.browser-evidence/v2` metrics with launched Chromium name,
|
|
112
|
-
version, and capture identity, each review request
|
|
144
|
+
version, and capture identity, each review request and closed local evidence
|
|
145
|
+
projection, cohesion
|
|
113
146
|
observations, and any one-pass revision record. Missing, malformed, forged,
|
|
114
147
|
cross-record mismatched, stale, or failed review-chain evidence terminates as
|
|
115
148
|
`built-needs-review`; durability and publication remain blocked.
|
|
149
|
+
The review chain performs at most one correction and one final review. It never
|
|
150
|
+
starts a second rebuild to chase visual perfection.
|
|
116
151
|
|
|
117
152
|
## Tracked-run finalization
|
|
118
153
|
|
|
@@ -124,10 +159,15 @@ repository root, project name, an explicit compatible `coreRoot`, and, for
|
|
|
124
159
|
dynamically loads the versioned package-coverage contract from that core root;
|
|
125
160
|
it does not maintain an adapter-local path list.
|
|
126
161
|
|
|
127
|
-
A core `built-needs-review` outcome is
|
|
128
|
-
non-durable success.
|
|
129
|
-
|
|
130
|
-
|
|
162
|
+
A core `built-needs-review` or `failed` outcome is terminal evidence, not a
|
|
163
|
+
non-durable success. Provider review and diagnostic prose exists only during
|
|
164
|
+
the in-memory correction attempt. Retained review attempts use the closed
|
|
165
|
+
`visual-review-evidence/v1` projection bound to the adjacent request hash and
|
|
166
|
+
attempt directory. The core retains `terminal-evidence.json` with run identity,
|
|
167
|
+
the manifest hash when available, bounded `stage`/`kind` reason tuples, and the
|
|
168
|
+
evidence disposition. The planner verifies that binding and returns complete
|
|
169
|
+
without artifact, attestation, evidence-commit, or push commands. This preserves
|
|
170
|
+
the handoff without promoting it to durable or publishable success.
|
|
131
171
|
|
|
132
172
|
The returned stages must run in order:
|
|
133
173
|
|
|
@@ -188,7 +228,11 @@ complete. A later attestation may recover durability without repeating the
|
|
|
188
228
|
archive.
|
|
189
229
|
|
|
190
230
|
This recovery path applies only to `built-not-durable`.
|
|
191
|
-
`built-needs-review`
|
|
231
|
+
`built-needs-review` and `failed` packages may be exported so their compact
|
|
232
|
+
terminal evidence survives project deletion, but they cannot be attested into
|
|
233
|
+
`built-durable`, pushed by the finalizer, or published. Flagged, failed,
|
|
234
|
+
superseded, and `built-not-durable` runs are never publishable. Only a
|
|
235
|
+
review-clean `built-durable` run may cross the explicit human publication gate.
|
|
192
236
|
|
|
193
237
|
Post-archive summary and PR recap links target `projectRecapExport.exportRoot`
|
|
194
238
|
under `.oat/repo/reference/project-recaps/` on the current head branch. The
|
|
@@ -17,7 +17,8 @@ release candidate passes every acceptance gate.
|
|
|
17
17
|
Google account data, authentication choices, and personal destination
|
|
18
18
|
topology in private wrapper configuration.
|
|
19
19
|
4. Invoke the core once, consume only `explainer-kit.manifest/v1` and the
|
|
20
|
-
optional publish
|
|
20
|
+
optional `explainer-kit.publish-receipt/v2` (while retaining v1 replay),
|
|
21
|
+
then perform Stoa notes, Google Docs sync, and
|
|
21
22
|
private link maintenance as post-run work.
|
|
22
23
|
|
|
23
24
|
Do not add a plugin, inject private work between core stages, or add private
|
|
@@ -51,6 +51,12 @@ viewport-matched screenshot and metrics hashes, observed cohesion claims, and
|
|
|
51
51
|
the exact browser runtime and capture identity shared by every evidence record.
|
|
52
52
|
Use the evidence reader to inspect the byte snapshot named by the request.
|
|
53
53
|
|
|
54
|
+
Judge the rendered set as prose-led visual work. Cover typography, hierarchy,
|
|
55
|
+
composition, density, medium leverage, template repetition, diagram semantics,
|
|
56
|
+
and cross-artifact cohesion. Use the set plan and shared ledger to distinguish
|
|
57
|
+
intentional variation from drift. Do not turn those judgments into numeric
|
|
58
|
+
scores, geometry thresholds, or deterministic style checks.
|
|
59
|
+
|
|
54
60
|
Return `explainer-kit.visual-review-result/v1` with:
|
|
55
61
|
|
|
56
62
|
- a safe `reviewId`;
|
|
@@ -60,6 +66,11 @@ Return `explainer-kit.visual-review-result/v1` with:
|
|
|
60
66
|
- the complete reviewed `artifactIds`; and
|
|
61
67
|
- structured `findings`.
|
|
62
68
|
|
|
69
|
+
Return `pass` only when the full set needs no required correction. Return
|
|
70
|
+
`correct` when one bounded review round can address every finding, and give
|
|
71
|
+
each finding an actionable correction tied to an artifact and visible evidence.
|
|
72
|
+
The result shape and its `pass`/`correct`/`fail` dispositions remain unchanged.
|
|
73
|
+
|
|
63
74
|
The callback must not mutate rendered files, screenshots, or metrics. The core
|
|
64
75
|
revalidates all bound bytes after the callback returns.
|
|
65
76
|
|
|
@@ -18,6 +18,12 @@ export async function bindProjectSources({
|
|
|
18
18
|
suppliedFactBasePath,
|
|
19
19
|
reviewedRepository,
|
|
20
20
|
}) {
|
|
21
|
+
if (suppliedFactBasePath) {
|
|
22
|
+
const repository =
|
|
23
|
+
reviewedRepository ??
|
|
24
|
+
(repoRoot ? await resolveReviewedRepository(repoRoot) : null);
|
|
25
|
+
return bindSuppliedFactBase(suppliedFactBasePath, repository);
|
|
26
|
+
}
|
|
21
27
|
if (!projectRoot) {
|
|
22
28
|
throw new TypeError('projectRoot is required to bind OAT artifacts.');
|
|
23
29
|
}
|
|
@@ -30,21 +36,6 @@ export async function bindProjectSources({
|
|
|
30
36
|
const repository =
|
|
31
37
|
reviewedRepository ??
|
|
32
38
|
(repoRoot ? await resolveReviewedRepository(repoRoot) : null);
|
|
33
|
-
if (suppliedFactBasePath) {
|
|
34
|
-
const path = await realpath(suppliedFactBasePath);
|
|
35
|
-
return {
|
|
36
|
-
factBase: {
|
|
37
|
-
mode: 'supplied',
|
|
38
|
-
path,
|
|
39
|
-
freshnessPolicy: 'live-wins',
|
|
40
|
-
},
|
|
41
|
-
reviewedSource: {
|
|
42
|
-
kind: 'approved-fact-base',
|
|
43
|
-
locator: repository?.repositoryUrl ?? path,
|
|
44
|
-
...(repository && repository),
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
39
|
|
|
49
40
|
const sourceSetId = basename(canonicalProjectRoot);
|
|
50
41
|
const sources = [];
|
|
@@ -95,6 +86,21 @@ export async function bindProjectSources({
|
|
|
95
86
|
};
|
|
96
87
|
}
|
|
97
88
|
|
|
89
|
+
export async function bindRepositorySources({
|
|
90
|
+
repoRoot,
|
|
91
|
+
suppliedFactBasePath,
|
|
92
|
+
reviewedRepository,
|
|
93
|
+
}) {
|
|
94
|
+
if (!suppliedFactBasePath) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
'Repository invocation requires a caller-supplied fact base path.',
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const repository =
|
|
100
|
+
reviewedRepository ?? (await resolveReviewedRepository(repoRoot));
|
|
101
|
+
return bindSuppliedFactBase(suppliedFactBasePath, repository);
|
|
102
|
+
}
|
|
103
|
+
|
|
98
104
|
export async function resolveReviewedRepository(
|
|
99
105
|
repoRoot,
|
|
100
106
|
{ command = execFile } = {},
|
|
@@ -119,6 +125,22 @@ export async function resolveReviewedRepository(
|
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
127
|
|
|
128
|
+
async function bindSuppliedFactBase(suppliedFactBasePath, repository) {
|
|
129
|
+
const path = await realpath(suppliedFactBasePath);
|
|
130
|
+
return {
|
|
131
|
+
factBase: {
|
|
132
|
+
mode: 'supplied',
|
|
133
|
+
path,
|
|
134
|
+
freshnessPolicy: 'live-wins',
|
|
135
|
+
},
|
|
136
|
+
reviewedSource: {
|
|
137
|
+
kind: 'approved-fact-base',
|
|
138
|
+
locator: repository?.repositoryUrl ?? path,
|
|
139
|
+
...(repository && repository),
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
122
144
|
async function loadOatArtifact(source) {
|
|
123
145
|
return oatArtifactDocument(
|
|
124
146
|
source,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
|
|
4
|
+
const TERMINAL_OUTCOMES = new Set([
|
|
5
|
+
'built-durable',
|
|
6
|
+
'built-not-durable',
|
|
7
|
+
'built-needs-review',
|
|
8
|
+
'failed',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export function checkTerminalOutcome({ intent, outcome }) {
|
|
12
|
+
if (intent === 'skip') {
|
|
13
|
+
return { ok: true, intent, outcome: null };
|
|
14
|
+
}
|
|
15
|
+
if (intent !== 'generate') {
|
|
16
|
+
throw recapOutcomeError('Recap intent must be generate or skip.');
|
|
17
|
+
}
|
|
18
|
+
if (!TERMINAL_OUTCOMES.has(outcome)) {
|
|
19
|
+
throw recapOutcomeError(
|
|
20
|
+
'Generated project recaps require a terminal recap outcome before approval.',
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return { ok: true, intent, outcome };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main(argv) {
|
|
27
|
+
const { intent, manifestPath } = parseArguments(argv);
|
|
28
|
+
let outcome;
|
|
29
|
+
if (manifestPath !== undefined) {
|
|
30
|
+
let manifest;
|
|
31
|
+
try {
|
|
32
|
+
manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw recapOutcomeError(
|
|
35
|
+
`Recap manifest could not be read: ${error.message}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
outcome = manifest?.outcome;
|
|
39
|
+
}
|
|
40
|
+
return checkTerminalOutcome({ intent, outcome });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseArguments(argv) {
|
|
44
|
+
let intent;
|
|
45
|
+
let manifestPath;
|
|
46
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
47
|
+
const flag = argv[index];
|
|
48
|
+
const value = argv[index + 1];
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
throw recapOutcomeError(`Missing value for ${flag ?? 'argument'}.`);
|
|
51
|
+
}
|
|
52
|
+
if (flag === '--intent') {
|
|
53
|
+
intent = value;
|
|
54
|
+
} else if (flag === '--manifest') {
|
|
55
|
+
manifestPath = value;
|
|
56
|
+
} else {
|
|
57
|
+
throw recapOutcomeError(`Unsupported argument: ${flag}.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { intent, manifestPath };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function recapOutcomeError(message) {
|
|
64
|
+
const error = new Error(message);
|
|
65
|
+
error.code = 'E_RECAP_OUTCOME';
|
|
66
|
+
return error;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
process.argv[1] &&
|
|
71
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
72
|
+
) {
|
|
73
|
+
main(process.argv.slice(2))
|
|
74
|
+
.then((result) => {
|
|
75
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
76
|
+
})
|
|
77
|
+
.catch((error) => {
|
|
78
|
+
process.stderr.write(
|
|
79
|
+
`${JSON.stringify({ ok: false, code: error.code, message: error.message })}\n`,
|
|
80
|
+
);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const INVOCATIONS = new Set(['project', 'repo', 'direct']);
|
|
2
|
+
|
|
3
|
+
export function deriveExplainerDestination({
|
|
4
|
+
invocation,
|
|
5
|
+
projectSlug,
|
|
6
|
+
s3Uri,
|
|
7
|
+
publicBaseUrl,
|
|
8
|
+
}) {
|
|
9
|
+
if (!INVOCATIONS.has(invocation)) {
|
|
10
|
+
throw new Error(`Unsupported explainer invocation: ${invocation}`);
|
|
11
|
+
}
|
|
12
|
+
const roots = {
|
|
13
|
+
s3Uri: normalizeRoot(s3Uri, 's3Uri', 's3:'),
|
|
14
|
+
publicBaseUrl: normalizeRoot(publicBaseUrl, 'publicBaseUrl', 'https:'),
|
|
15
|
+
};
|
|
16
|
+
if (invocation !== 'project') {
|
|
17
|
+
return roots;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const encodedSlug = encodeProjectSlug(projectSlug);
|
|
21
|
+
return {
|
|
22
|
+
s3Uri: `${roots.s3Uri}/projects/${encodedSlug}`,
|
|
23
|
+
publicBaseUrl: `${roots.publicBaseUrl}/projects/${encodedSlug}`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function encodeProjectSlug(projectSlug) {
|
|
28
|
+
if (
|
|
29
|
+
typeof projectSlug !== 'string' ||
|
|
30
|
+
!projectSlug.trim() ||
|
|
31
|
+
projectSlug === '.' ||
|
|
32
|
+
projectSlug === '..' ||
|
|
33
|
+
projectSlug.includes('/') ||
|
|
34
|
+
projectSlug.includes('\\') ||
|
|
35
|
+
projectSlug.includes('\0')
|
|
36
|
+
) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'Project slug must be a non-empty, safe path segment without traversal or separators.',
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return encodeURIComponent(projectSlug);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeRoot(value, label, protocol) {
|
|
45
|
+
if (typeof value !== 'string') {
|
|
46
|
+
throw new Error(`${label} must be a valid destination root.`);
|
|
47
|
+
}
|
|
48
|
+
const root = value.trim();
|
|
49
|
+
const authorityStart = `${protocol}//`;
|
|
50
|
+
if (
|
|
51
|
+
!root.startsWith(authorityStart) ||
|
|
52
|
+
/\s|\\/.test(root) ||
|
|
53
|
+
root.includes('?') ||
|
|
54
|
+
root.includes('#')
|
|
55
|
+
) {
|
|
56
|
+
throw new Error(`${label} must be a valid destination root.`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = new URL(root);
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error(`${label} must be a valid destination root.`);
|
|
64
|
+
}
|
|
65
|
+
const authority = root.slice(authorityStart.length).split('/', 1)[0];
|
|
66
|
+
if (
|
|
67
|
+
parsed.protocol !== protocol ||
|
|
68
|
+
!isValidRawAuthority(authority, protocol) ||
|
|
69
|
+
!parsed.hostname ||
|
|
70
|
+
parsed.username ||
|
|
71
|
+
parsed.password ||
|
|
72
|
+
parsed.search ||
|
|
73
|
+
parsed.hash
|
|
74
|
+
) {
|
|
75
|
+
throw new Error(`${label} must be a valid destination root.`);
|
|
76
|
+
}
|
|
77
|
+
return root.replace(/\/+$/, '');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isValidRawAuthority(authority, protocol) {
|
|
81
|
+
if (!authority || authority.includes('@')) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (protocol === 's3:') {
|
|
85
|
+
return /^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(authority);
|
|
86
|
+
}
|
|
87
|
+
if (authority.startsWith('[')) {
|
|
88
|
+
return /^\[[^\]]+\](?::\d+)?$/.test(authority);
|
|
89
|
+
}
|
|
90
|
+
return /^[^:]+(?::\d+)?$/.test(authority);
|
|
91
|
+
}
|
|
@@ -7,6 +7,7 @@ const MODES = new Set(['dedicated', 'completion-bookkeeping']);
|
|
|
7
7
|
const ARTIFACT_COMMIT_TOKEN = '$ARTIFACT_COMMIT';
|
|
8
8
|
const SHA_PATTERN = /^[a-f0-9]{40}$/;
|
|
9
9
|
const PACKAGE_COVERAGE_VERSION = 'explainer-kit.package-coverage/v2';
|
|
10
|
+
const TERMINAL_EVIDENCE_VERSION = 'explainer-kit.terminal-evidence/v1';
|
|
10
11
|
|
|
11
12
|
export async function planTrackedRunFinalization(request, context = {}) {
|
|
12
13
|
assertRequest(request);
|
|
@@ -24,10 +25,27 @@ export async function planTrackedRunFinalization(request, context = {}) {
|
|
|
24
25
|
'Finalization requires an explainer-kit.manifest/v1 record.',
|
|
25
26
|
);
|
|
26
27
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
if (['built-needs-review', 'failed'].includes(manifest.outcome)) {
|
|
29
|
+
const terminalEvidenceContract = await loadTerminalEvidenceContract(
|
|
30
|
+
context.coreRoot,
|
|
30
31
|
);
|
|
32
|
+
const { evidence: terminalEvidence } =
|
|
33
|
+
await terminalEvidenceContract.readTerminalEvidenceFile(runRoot, {
|
|
34
|
+
manifest,
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
schemaVersion: 'oat-explainer-kit.finalization-plan/v1',
|
|
38
|
+
status: 'complete',
|
|
39
|
+
outcome: manifest.outcome,
|
|
40
|
+
commands: [],
|
|
41
|
+
push: null,
|
|
42
|
+
publicationAllowed: false,
|
|
43
|
+
evidenceDisposition: terminalEvidence.evidenceDisposition,
|
|
44
|
+
terminalEvidencePath: 'terminal-evidence.json',
|
|
45
|
+
...(terminalEvidence.supersededBy && {
|
|
46
|
+
supersededBy: terminalEvidence.supersededBy,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
31
49
|
}
|
|
32
50
|
const packageCoverage = await loadPackageCoverage(context.coreRoot);
|
|
33
51
|
|
|
@@ -138,11 +156,21 @@ export async function planTrackedRunFinalization(request, context = {}) {
|
|
|
138
156
|
|
|
139
157
|
export function verifyTrackedRunFinalization(plan, observation) {
|
|
140
158
|
if (plan?.status === 'complete') {
|
|
159
|
+
if (
|
|
160
|
+
!['built-durable', 'built-needs-review', 'failed'].includes(plan.outcome)
|
|
161
|
+
) {
|
|
162
|
+
return failed([
|
|
163
|
+
error(
|
|
164
|
+
'invalid-plan',
|
|
165
|
+
'Complete finalization plan has an unsupported outcome.',
|
|
166
|
+
),
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
141
169
|
return {
|
|
142
170
|
ok: true,
|
|
143
|
-
outcome:
|
|
171
|
+
outcome: plan.outcome,
|
|
144
172
|
pushAllowed: false,
|
|
145
|
-
|
|
173
|
+
reasons: [],
|
|
146
174
|
};
|
|
147
175
|
}
|
|
148
176
|
const errors = [];
|
|
@@ -240,7 +268,7 @@ export function verifyTrackedRunFinalization(plan, observation) {
|
|
|
240
268
|
}
|
|
241
269
|
|
|
242
270
|
return errors.length === 0
|
|
243
|
-
? { ok: true, outcome, pushAllowed: true,
|
|
271
|
+
? { ok: true, outcome, pushAllowed: true, reasons: [] }
|
|
244
272
|
: failed(errors);
|
|
245
273
|
}
|
|
246
274
|
|
|
@@ -329,10 +357,9 @@ async function loadPackageCoverage(coreRoot) {
|
|
|
329
357
|
let loaded;
|
|
330
358
|
try {
|
|
331
359
|
loaded = await import(pathToFileURL(modulePath).href);
|
|
332
|
-
} catch
|
|
360
|
+
} catch {
|
|
333
361
|
throw new Error(
|
|
334
|
-
|
|
335
|
-
{ cause: loadError },
|
|
362
|
+
'Compatible explainer package coverage could not be loaded from coreRoot.',
|
|
336
363
|
);
|
|
337
364
|
}
|
|
338
365
|
if (
|
|
@@ -347,6 +374,29 @@ async function loadPackageCoverage(coreRoot) {
|
|
|
347
374
|
return loaded;
|
|
348
375
|
}
|
|
349
376
|
|
|
377
|
+
async function loadTerminalEvidenceContract(coreRoot) {
|
|
378
|
+
const root = await realpathRequired(coreRoot, 'coreRoot');
|
|
379
|
+
const modulePath = join(root, 'scripts', 'lib', 'terminal-evidence.mjs');
|
|
380
|
+
let loaded;
|
|
381
|
+
try {
|
|
382
|
+
loaded = await import(pathToFileURL(modulePath).href);
|
|
383
|
+
} catch {
|
|
384
|
+
throw new Error(
|
|
385
|
+
'Compatible terminal evidence could not be loaded from coreRoot.',
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (
|
|
389
|
+
loaded.TERMINAL_EVIDENCE_VERSION !== TERMINAL_EVIDENCE_VERSION ||
|
|
390
|
+
typeof loaded.assertTerminalEvidence !== 'function' ||
|
|
391
|
+
typeof loaded.readTerminalEvidenceFile !== 'function'
|
|
392
|
+
) {
|
|
393
|
+
throw new Error(
|
|
394
|
+
`coreRoot must provide ${TERMINAL_EVIDENCE_VERSION} confined validation.`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return loaded;
|
|
398
|
+
}
|
|
399
|
+
|
|
350
400
|
function resolveRunPath(runRoot, path) {
|
|
351
401
|
const target = resolve(runRoot, path);
|
|
352
402
|
assertWithin(runRoot, target, `Manifest path ${path}`);
|
|
@@ -470,6 +520,12 @@ function failed(errors) {
|
|
|
470
520
|
ok: false,
|
|
471
521
|
outcome: 'built-not-durable',
|
|
472
522
|
pushAllowed: false,
|
|
473
|
-
|
|
523
|
+
reasons: [
|
|
524
|
+
{
|
|
525
|
+
stage: 'finalization',
|
|
526
|
+
kind: 'pipeline-failure',
|
|
527
|
+
count: Math.min(Math.max(errors.length, 1), 50),
|
|
528
|
+
},
|
|
529
|
+
],
|
|
474
530
|
};
|
|
475
531
|
}
|