@open-agent-toolkit/cli 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/assets/docs/cli-utilities/configuration.md +40 -0
  2. package/assets/docs/cli-utilities/tool-packs.md +31 -0
  3. package/assets/docs/workflows/projects/artifacts.md +27 -0
  4. package/assets/docs/workflows/skills/explainer-kit.md +125 -0
  5. package/assets/docs/workflows/skills/index.md +4 -0
  6. package/assets/migration/pjm-restructure.md +12 -0
  7. package/assets/public-package-versions.json +4 -4
  8. package/assets/skills/explainer-kit/SKILL.md +95 -0
  9. package/assets/skills/explainer-kit/examples/project-explainer/content.md +31 -0
  10. package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +22 -0
  11. package/assets/skills/explainer-kit/examples/project-recap/content.md +34 -0
  12. package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +22 -0
  13. package/assets/skills/explainer-kit/examples/theme-bundle.json +87 -0
  14. package/assets/skills/explainer-kit/palettes/ember.json +37 -0
  15. package/assets/skills/explainer-kit/palettes/forest.json +37 -0
  16. package/assets/skills/explainer-kit/palettes/neutral.json +37 -0
  17. package/assets/skills/explainer-kit/palettes/ocean.json +37 -0
  18. package/assets/skills/explainer-kit/palettes/violet.json +37 -0
  19. package/assets/skills/explainer-kit/profiles/clean.json +42 -0
  20. package/assets/skills/explainer-kit/profiles/editorial.json +42 -0
  21. package/assets/skills/explainer-kit/profiles/technical.json +39 -0
  22. package/assets/skills/explainer-kit/recipes/engineer-tour.json +33 -0
  23. package/assets/skills/explainer-kit/recipes/program-recap.json +34 -0
  24. package/assets/skills/explainer-kit/recipes/project-explainer.json +33 -0
  25. package/assets/skills/explainer-kit/recipes/project-recap.json +34 -0
  26. package/assets/skills/explainer-kit/references/contracts.md +101 -0
  27. package/assets/skills/explainer-kit/references/destination-contract.md +94 -0
  28. package/assets/skills/explainer-kit/references/extension-contract.md +71 -0
  29. package/assets/skills/explainer-kit/references/fact-base-contract.md +119 -0
  30. package/assets/skills/explainer-kit/schemas/build-record.schema.json +92 -0
  31. package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +53 -0
  32. package/assets/skills/explainer-kit/schemas/fact-base.schema.json +129 -0
  33. package/assets/skills/explainer-kit/schemas/manifest.schema.json +193 -0
  34. package/assets/skills/explainer-kit/schemas/publish-receipt.schema.json +97 -0
  35. package/assets/skills/explainer-kit/schemas/publish-request.schema.json +32 -0
  36. package/assets/skills/explainer-kit/schemas/run-request.schema.json +132 -0
  37. package/assets/skills/explainer-kit/schemas/theme.schema.json +217 -0
  38. package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +180 -0
  39. package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +680 -0
  40. package/assets/skills/explainer-kit/scripts/lib/durability.mjs +538 -0
  41. package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +494 -0
  42. package/assets/skills/explainer-kit/scripts/lib/fs-safe.mjs +218 -0
  43. package/assets/skills/explainer-kit/scripts/lib/qa.mjs +512 -0
  44. package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +289 -0
  45. package/assets/skills/explainer-kit/scripts/lib/records.mjs +231 -0
  46. package/assets/skills/explainer-kit/scripts/lib/render.mjs +485 -0
  47. package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +587 -0
  48. package/assets/skills/explainer-kit/scripts/lib/safe-paths.mjs +96 -0
  49. package/assets/skills/explainer-kit/scripts/lib/theme.mjs +332 -0
  50. package/assets/skills/explainer-kit/scripts/publish.mjs +76 -0
  51. package/assets/skills/explainer-kit/scripts/record-durability.mjs +64 -0
  52. package/assets/skills/explainer-kit/scripts/render-qa.mjs +273 -0
  53. package/assets/skills/explainer-kit/scripts/run.mjs +778 -0
  54. package/assets/skills/explainer-kit/scripts/validate.mjs +59 -0
  55. package/assets/skills/explainer-kit/templates/deck-shell.html +233 -0
  56. package/assets/skills/explainer-kit/templates/diagram-shell.html +219 -0
  57. package/assets/skills/explainer-kit/templates/engineer-tour.html +273 -0
  58. package/assets/skills/explainer-kit/templates/house-style.html +218 -0
  59. package/assets/skills/oat-explainer-kit/SKILL.md +92 -0
  60. package/assets/skills/oat-explainer-kit/references/config-contract.md +67 -0
  61. package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +143 -0
  62. package/assets/skills/oat-explainer-kit/references/migration.md +143 -0
  63. package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +103 -0
  64. package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +127 -0
  65. package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +392 -0
  66. package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +130 -0
  67. package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +293 -0
  68. package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +209 -0
  69. package/assets/skills/oat-explainer-kit/scripts/resolve-paths.mjs +166 -0
  70. package/assets/skills/oat-explainer-kit/scripts/run.mjs +284 -0
  71. package/assets/skills/oat-project-autonomous/SKILL.md +22 -1
  72. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +5 -1
  73. package/assets/skills/oat-project-complete/SKILL.md +166 -28
  74. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +5 -1
  75. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  76. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +17 -0
  77. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +5 -1
  78. package/assets/skills/oat-project-plan/SKILL.md +30 -1
  79. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +5 -1
  80. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +5 -1
  81. package/assets/skills/oat-project-summary/SKILL.md +20 -1
  82. package/dist/commands/config/index.d.ts.map +1 -1
  83. package/dist/commands/config/index.js +232 -1
  84. package/dist/commands/init/tools/shared/skill-manifest.d.ts +2 -2
  85. package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
  86. package/dist/commands/init/tools/shared/skill-manifest.js +2 -0
  87. package/dist/commands/project/archive/archive-utils.d.ts +12 -0
  88. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  89. package/dist/commands/project/archive/archive-utils.js +320 -8
  90. package/dist/commands/project/archive/index.d.ts.map +1 -1
  91. package/dist/commands/project/archive/index.js +1 -0
  92. package/dist/commands/project/archive/push-runner.d.ts +1 -0
  93. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  94. package/dist/commands/project/archive/push-runner.js +9 -2
  95. package/dist/config/oat-config.d.ts +26 -0
  96. package/dist/config/oat-config.d.ts.map +1 -1
  97. package/dist/config/oat-config.js +88 -0
  98. package/dist/config/resolve.d.ts.map +1 -1
  99. package/dist/config/resolve.js +18 -0
  100. package/dist/validation/project-state.d.ts +3 -0
  101. package/dist/validation/project-state.d.ts.map +1 -1
  102. package/dist/validation/project-state.js +91 -0
  103. package/package.json +2 -2
@@ -0,0 +1,143 @@
1
+ # Lifecycle intent contract
2
+
3
+ The OAT adapter resolves `projectExplainer` and `projectRecap` intent without
4
+ reading or mutating project files. Lifecycle callers pass explicit inputs to
5
+ `resolveIntent(...)` and persist only the returned `record`, when present.
6
+
7
+ ## Resolution
8
+
9
+ Precedence is:
10
+
11
+ 1. lifecycle mode policy;
12
+ 2. valid project state;
13
+ 3. `workflow.explainers.*` preference;
14
+ 4. the built-in `ask` default.
15
+
16
+ `resolveIntent({ product, mode, state, preference, kickoffRequest, answer,
17
+ now })` is pure. Its result contains the effective `decision`, the
18
+ `resolutionSource`, whether a prompt is needed, an optional state `record`, and
19
+ warnings.
20
+
21
+ In autonomous mode, `projectRecap` always resolves to `generate` with source
22
+ `autonomous_policy`. A lower-precedence skip or `never` preference is overridden
23
+ and reported as a warning. Autonomous `projectExplainer` resolves to `generate`
24
+ only when the kickoff prompt explicitly requested it; otherwise it resolves to
25
+ `skip` without writing an invalid prompt-source skip record.
26
+
27
+ In interactive mode, an existing valid project record prevents another prompt.
28
+ Preferences `always` and `never` resolve directly but are not copied into
29
+ project state: doing so would freeze a workflow preference snapshot.
30
+ An unresolved `ask` prompts once. Either answer produces an `interactive`
31
+ record, so a decision made at any lifecycle gate can be persisted and reused.
32
+
33
+ ## State records
34
+
35
+ Records use the Phase 1 state contract:
36
+
37
+ ```yaml
38
+ oat_project_explainer:
39
+ decision: generate
40
+ source: kickoff_prompt
41
+ decided_at: '2026-07-18T02:30:00Z'
42
+ oat_project_recap:
43
+ decision: generate
44
+ source: autonomous_policy
45
+ decided_at: '2026-07-18T02:30:00Z'
46
+ ```
47
+
48
+ Allowed decision/source pairs are:
49
+
50
+ | Product | Allowed pairs |
51
+ | ------------------ | ------------------------------------------------------------------------ |
52
+ | `projectExplainer` | `generate/interactive`, `skip/interactive`, `generate/kickoff_prompt` |
53
+ | `projectRecap` | `generate/interactive`, `skip/interactive`, `generate/autonomous_policy` |
54
+
55
+ In particular, `skip/autonomous_policy` is invalid.
56
+
57
+ ## Safe persistence
58
+
59
+ `hashStateContent(content)` creates the optimistic concurrency token used by
60
+ `persistIntent(...)`. A caller reads `state.md`, resolves intent, and supplies
61
+ that content hash with the chosen record. Persistence:
62
+
63
+ - accepts only a regular `state.md` file and rejects symlinks;
64
+ - validates the closed record and product-specific source matrix;
65
+ - rejects a changed file with `E_INTENT_STALE_WRITE`;
66
+ - replaces only the selected top-level intent block while preserving unrelated
67
+ frontmatter fields and the Markdown body; and
68
+ - writes a same-directory temporary file and atomically renames it.
69
+
70
+ On a stale-write conflict, the caller must re-read state, resolve precedence
71
+ again, and decide whether a write is still required. It must not retry the old
72
+ record blindly.
73
+
74
+ ## Tracked-run finalization
75
+
76
+ `planTrackedRunFinalization(request, context)` is the shared command planner for
77
+ tracked project explainer and recap runs. The request contains `runRoot`,
78
+ `manifestPath`, `commitMode`, and optional `relocatedFrom`. Context supplies the
79
+ repository root, project name, and, for `completion-bookkeeping`, the existing
80
+ full artifact commit SHA.
81
+
82
+ The returned stages must run in order:
83
+
84
+ 1. In `dedicated` mode, commit exactly the manifest-declared immutable package
85
+ with `docs(oat): persist <recipe> for <project>`. In
86
+ `completion-bookkeeping` mode, reuse the caller's existing lifecycle commit.
87
+ 2. Replace `$ARTIFACT_COMMIT` with the created full SHA when present, then pass
88
+ the planned durability request to the compatible core's
89
+ `recordDurability(...)`. The core verifies commit blobs and updates records;
90
+ it never invokes Git or creates commits.
91
+ 3. Commit only `manifest.json` and `build-record.json` as the evidence update.
92
+ 4. Call `verifyTrackedRunFinalization(...)`, then push once so the artifact and
93
+ evidence commits travel together.
94
+
95
+ Artifact evidence contains retained fact-base, content, theme, and rendered
96
+ paths. It always excludes mutable `manifest.json` and `build-record.json`.
97
+ Generated Git commands use explicit pathspecs and `commit --only`; callers must
98
+ also snapshot unrelated working-tree changes before execution and supply the
99
+ before/after lists to the verifier. A mismatch prevents pushing.
100
+
101
+ An evidence-verification failure is a successful finalizer termination with
102
+ run outcome `built-not-durable`: commit the warning-bearing mutable records and
103
+ push them with the artifact commit. It does not block project completion. A
104
+ later attempt reuses the same artifact commit, supplies the current HEAD as
105
+ `currentHead`, invokes core verification again, and appends a new evidence
106
+ commit. If the manifest already contains matching durable commit evidence, the
107
+ planner returns `complete` with no commands, making repeat termination
108
+ idempotent.
109
+
110
+ For archive relocation, `relocatedFrom` identifies the prior active run for
111
+ caller reporting. The current run's immutable paths and the export bookkeeping
112
+ commit are submitted to the core; core evidence supersession remains the
113
+ authoritative relocation record.
114
+
115
+ ## Completion-time archive relocation
116
+
117
+ Completion consumes the machine-readable `oat project archive --json` report.
118
+ When a recap was selected, `projectRecapExport.sourceRunRoot`,
119
+ `projectRecapExport.exportRoot`, and
120
+ `projectRecapExport.manifest.relativePath` identify the relocation. The caller
121
+ must not predict the dated export path or substitute the gitignored local
122
+ archive.
123
+
124
+ Archive completion is exactly two commits: the lifecycle bookkeeping commit, then the exported recap evidence commit. The bookkeeping commit contains the
125
+ tracked export and active-tree deletion and is passed to the finalizer as the
126
+ existing artifact commit in `completion-bookkeeping` mode. The finalizer
127
+ attests only immutable package paths under the reported export root. The
128
+ second commit contains only the updated exported `manifest.json` and
129
+ `build-record.json`; one push follows both commits.
130
+
131
+ The exported-path evidence supersedes the selected run's prior active-path evidence. Mutable records are never part of their own commit evidence, and no
132
+ path under `.oat/projects/archived/` is evidence.
133
+
134
+ Failure to verify the exported commit evidence is non-blocking. The tracked
135
+ export remains committed, the mutable records retain the warning and
136
+ `built-not-durable` outcome, and the evidence-record commit and push still
137
+ complete. A later attestation may recover durability without repeating the
138
+ archive.
139
+
140
+ Post-archive summary and PR recap links target `projectRecapExport.exportRoot`
141
+ under `.oat/repo/reference/project-recaps/` on the current head branch. The
142
+ tracked summary export and the PR body may carry that link; the gitignored
143
+ archive never does.
@@ -0,0 +1,143 @@
1
+ # Private-wrapper migration
2
+
3
+ This runbook migrates the externally owned private wrapper from the monolithic
4
+ `oat-explainer-kit` 0.4.1 workflow to the frozen v1 pre/core/post seam. Keep the
5
+ 0.4.1 installation and its private configuration available until one unchanged
6
+ release candidate passes every acceptance gate.
7
+
8
+ ## Prepare the private wrapper
9
+
10
+ 1. Back up the installed 0.4.1 skill, private presets, Stoa configuration, and
11
+ Google Docs settings. Confirm the backup contains `SKILL.md`, scripts,
12
+ references, and templates.
13
+ 2. Install the candidate `explainer-kit` at user scope and verify that the
14
+ wrapper resolves that installed copy, never a source checkout.
15
+ 3. Change wrapper pre-resolution to produce one
16
+ `explainer-kit.run-request/v1`. Keep preset selection, vault/Stoa paths,
17
+ Google account data, authentication choices, and personal destination
18
+ topology in private wrapper configuration.
19
+ 4. Invoke the core once, consume only `explainer-kit.manifest/v1` and the
20
+ optional publish receipt, then perform Stoa notes, Google Docs sync, and
21
+ private link maintenance as post-run work.
22
+
23
+ Do not add a plugin, inject private work between core stages, or add private
24
+ lanes to public OAT config.
25
+
26
+ ### Private `presets.example.json`
27
+
28
+ The private wrapper's `personal-oat` example should carry the confirmed public
29
+ root:
30
+
31
+ ```json
32
+ {
33
+ "presets": {
34
+ "personal-oat": {
35
+ "publicBaseUrl": "https://dy4vzrzaexuy5.cloudfront.net"
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ Copy `presets.example.json` to an untracked `presets.json` before adding
42
+ accounts or authentication details.
43
+
44
+ ### Private Stoa configuration
45
+
46
+ The eventual private Stoa configuration may use the same confirmed public root
47
+ to create manifest-derived links:
48
+
49
+ ```json
50
+ {
51
+ "explainerLinks": {
52
+ "publicBaseUrl": "https://dy4vzrzaexuy5.cloudfront.net",
53
+ "source": "explainer-kit.manifest/v1"
54
+ }
55
+ }
56
+ ```
57
+
58
+ This is private-wrapper migration/config context, not a public core default or
59
+ neutral fixture.
60
+
61
+ ## Release-candidate sequence
62
+
63
+ 1. Build and retain the packaged RC in a dedicated builder-owned directory;
64
+ record its immutable RC identity:
65
+
66
+ ```bash
67
+ node tools/release/build-explainer-rc.mjs \
68
+ --output dist/explainer-kit-rc \
69
+ --record .oat/repo/reference/explainer-kit-acceptance/v1/rc.json
70
+ ```
71
+
72
+ The builder rejects repository/source roots, symlinks, and unowned existing
73
+ directories. Reuse only an output carrying its ownership marker.
74
+
75
+ 2. Run packaged direct-core and OAT-adapter build-only smoke tests.
76
+ 3. Migrate the real private wrapper against that exact RC.
77
+ 4. Run the operator-owned wrapper E2E as an executable pre/core/post sequence:
78
+ resolve the private preset and external request; invoke the packaged core;
79
+ then publish the manifest, retain its complete receipt, and perform
80
+ vault/Stoa, Google Docs, and personal-link work. Verify all post-run evidence
81
+ against the immutable core execution record.
82
+ 5. Run the live S3/CDN acceptance against the same unchanged RC.
83
+ 6. Promote only when both retained acceptance records pass and reference the
84
+ same RC identity.
85
+
86
+ Every packaged invocation supplies the retained artifacts explicitly; there is
87
+ no current-working-directory fallback. The wrapper core stage does not declare
88
+ post-run receipt evidence:
89
+
90
+ ```bash
91
+ node tools/release/run-explainer-rc.mjs \
92
+ --rc-manifest .oat/repo/reference/explainer-kit-acceptance/v1/rc.json \
93
+ --artifacts-dir dist/explainer-kit-rc \
94
+ --entry scripts/run.mjs \
95
+ --record .oat/repo/reference/explainer-kit-acceptance/v1/private-wrapper-execution.json \
96
+ -- \
97
+ --request /path/to/private/run-request.json
98
+ ```
99
+
100
+ The packaged CLI emits exactly one complete JSON result document; pretty
101
+ printing across lines is valid and progress text is not. After that command
102
+ returns, the private wrapper publishes and links the core manifest. Retain
103
+ `private-wrapper-manifest.json`,
104
+ `private-wrapper-publish-receipt.json`, and
105
+ `private-wrapper-result.json` beside the RC identity. Wrapper acceptance reads
106
+ and validates the full receipt as a separate post-run stage and rejects a
107
+ foreign run ID, sentinel, artifact set, or manifest hash even when caller-owned
108
+ files repeat the same receipt hash.
109
+
110
+ The live connector stage is a separate packaged invocation:
111
+
112
+ ```bash
113
+ node tools/release/run-explainer-rc.mjs \
114
+ --rc-manifest .oat/repo/reference/explainer-kit-acceptance/v1/rc.json \
115
+ --artifacts-dir dist/explainer-kit-rc \
116
+ --entry scripts/publish.mjs \
117
+ --record .oat/repo/reference/explainer-kit-acceptance/v1/live-publish-result.json \
118
+ -- \
119
+ --request .oat/repo/reference/explainer-kit-acceptance/v1/live-publish-request.json \
120
+ --receipt .oat/repo/reference/explainer-kit-acceptance/v1/publish-receipt.json \
121
+ --confirm-publish
122
+ ```
123
+
124
+ Retain the execution record produced by that command. Acceptance binds it to
125
+ the canonical request, manifest, receipt, and core run ID and rejects stale
126
+ cross-run evidence.
127
+
128
+ The in-repository compatibility fixture is a development guard. It does not
129
+ satisfy the operator-owned real-wrapper gate.
130
+
131
+ ## Rollback
132
+
133
+ If migration or acceptance fails, do not promote the candidate:
134
+
135
+ 1. Preserve the failed candidate's sanitized diagnostics and issue a new RC
136
+ only after correcting the public seam or private wrapper.
137
+ 2. Restore the backed-up 0.4.1 skill and private configuration.
138
+ 3. Remove the candidate user-scope core and refresh provider views with
139
+ `oat sync --scope all`.
140
+ 4. Verify one known-good 0.4.1 build before resuming normal wrapper use.
141
+
142
+ Do not retire the backup until the unchanged promoted RC has passed the
143
+ operator-owned wrapper and live-publish gates.
@@ -0,0 +1,103 @@
1
+ import { access, readFile, realpath } from 'node:fs/promises';
2
+ import { basename, join } from 'node:path';
3
+
4
+ const RECIPE_ARTIFACTS = Object.freeze({
5
+ 'project-explainer': ['plan', 'design', 'spec'],
6
+ 'project-recap': ['plan', 'design', 'spec', 'implementation', 'summary'],
7
+ });
8
+
9
+ export async function bindProjectSources({
10
+ projectRoot,
11
+ recipe,
12
+ suppliedFactBasePath,
13
+ }) {
14
+ if (!projectRoot) {
15
+ throw new TypeError('projectRoot is required to bind OAT artifacts.');
16
+ }
17
+ const artifactIds = RECIPE_ARTIFACTS[recipe];
18
+ if (!artifactIds) {
19
+ throw new Error(`Unsupported OAT project recipe: ${recipe}`);
20
+ }
21
+
22
+ const canonicalProjectRoot = await realpath(projectRoot);
23
+ if (suppliedFactBasePath) {
24
+ const path = await realpath(suppliedFactBasePath);
25
+ return {
26
+ factBase: {
27
+ mode: 'supplied',
28
+ path,
29
+ freshnessPolicy: 'live-wins',
30
+ },
31
+ reviewedSource: {
32
+ kind: 'approved-fact-base',
33
+ locator: path,
34
+ },
35
+ };
36
+ }
37
+
38
+ const sourceSetId = basename(canonicalProjectRoot);
39
+ const sources = [];
40
+ for (const id of artifactIds) {
41
+ const locator = join(canonicalProjectRoot, `${id}.md`);
42
+ try {
43
+ await access(locator);
44
+ } catch (error) {
45
+ if (error?.code === 'ENOENT') continue;
46
+ throw error;
47
+ }
48
+ sources.push({
49
+ id,
50
+ kind: 'file',
51
+ locator: await realpath(locator),
52
+ role: 'project',
53
+ sourceSetId,
54
+ authoritativeFor: authoritativeTopics(id),
55
+ });
56
+ }
57
+ if (sources.length === 0) {
58
+ throw new Error(
59
+ `No approved OAT lifecycle artifacts were found for ${recipe}.`,
60
+ );
61
+ }
62
+
63
+ return {
64
+ factBase: {
65
+ mode: 'federated',
66
+ freshnessPolicy: 'live-wins',
67
+ sources,
68
+ },
69
+ reviewedSource: {
70
+ kind: 'approved-oat-artifacts',
71
+ locator: canonicalProjectRoot,
72
+ },
73
+ sourceLoader: loadOatArtifact,
74
+ };
75
+ }
76
+
77
+ async function loadOatArtifact(source) {
78
+ const text = (await readFile(source.locator, 'utf8')).trim();
79
+ return {
80
+ claims: [
81
+ {
82
+ id: source.id,
83
+ text,
84
+ locator: source.locator,
85
+ },
86
+ ],
87
+ };
88
+ }
89
+
90
+ function authoritativeTopics(id) {
91
+ return {
92
+ plan: ['phases', 'validation-approach'],
93
+ design: ['planned-architecture', 'decisions', 'risks'],
94
+ spec: ['original-request', 'requirements'],
95
+ implementation: [
96
+ 'key-agent-decisions',
97
+ 'as-built-architecture',
98
+ 'implementation-record',
99
+ 'validation-evidence',
100
+ ],
101
+ summary: ['outcome'],
102
+ }[id];
103
+ }
@@ -0,0 +1,127 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+
5
+ export const CORE_INSTALL_COMMAND = 'oat tools install utility --scope user';
6
+ export const CORE_UPDATE_COMMAND =
7
+ 'oat tools update --pack utility --scope user';
8
+
9
+ export async function checkCoreCompatibility({
10
+ adapterRoot,
11
+ userSkillsRoot = join(homedir(), '.agents', 'skills'),
12
+ minimumVersion,
13
+ }) {
14
+ const minimum = parseVersion(minimumVersion);
15
+ if (minimum === null) {
16
+ throw new TypeError(
17
+ `minimumVersion must be a semantic version, received: ${minimumVersion}`,
18
+ );
19
+ }
20
+
21
+ const canonicalAdapterRoot = resolve(adapterRoot);
22
+ const adapterSkillsRoot = dirname(canonicalAdapterRoot);
23
+ const canonicalUserSkillsRoot = resolve(userSkillsRoot);
24
+ const coreRoot = join(canonicalUserSkillsRoot, 'explainer-kit');
25
+ if (
26
+ basename(canonicalAdapterRoot) !== 'oat-explainer-kit' ||
27
+ basename(adapterSkillsRoot) !== 'skills' ||
28
+ basename(dirname(adapterSkillsRoot)) !== '.agents' ||
29
+ basename(canonicalUserSkillsRoot) !== 'skills' ||
30
+ basename(dirname(canonicalUserSkillsRoot)) !== '.agents'
31
+ ) {
32
+ return failure({
33
+ code: 'invalid-layout',
34
+ coreRoot,
35
+ minimumVersion,
36
+ message:
37
+ 'oat-explainer-kit is not running from an installed canonical .agents/skills path.',
38
+ guidance: CORE_INSTALL_COMMAND,
39
+ });
40
+ }
41
+
42
+ let skill;
43
+ try {
44
+ skill = await readFile(join(coreRoot, 'SKILL.md'), 'utf8');
45
+ } catch (error) {
46
+ if (error && typeof error === 'object' && error.code === 'ENOENT') {
47
+ return failure({
48
+ code: 'missing',
49
+ coreRoot,
50
+ minimumVersion,
51
+ message: 'A canonical installed explainer-kit core was not found.',
52
+ guidance: CORE_INSTALL_COMMAND,
53
+ });
54
+ }
55
+ throw error;
56
+ }
57
+
58
+ const installedVersion = readFrontmatterVersion(skill);
59
+ const installed = parseVersion(installedVersion);
60
+ if (
61
+ installed === null ||
62
+ installed.major !== minimum.major ||
63
+ installed.minor < minimum.minor
64
+ ) {
65
+ return failure({
66
+ code: 'incompatible',
67
+ coreRoot,
68
+ installedVersion,
69
+ minimumVersion,
70
+ message: installedVersion
71
+ ? `Installed explainer-kit ${installedVersion} is incompatible with required ${minimumVersion}.`
72
+ : 'Installed explainer-kit has no valid version.',
73
+ guidance: CORE_UPDATE_COMMAND,
74
+ });
75
+ }
76
+
77
+ return {
78
+ ok: true,
79
+ code: 'compatible',
80
+ coreRoot,
81
+ installedVersion,
82
+ minimumVersion,
83
+ message: `Installed explainer-kit ${installedVersion} is compatible.`,
84
+ guidance: null,
85
+ };
86
+ }
87
+
88
+ function failure({
89
+ code,
90
+ coreRoot,
91
+ installedVersion = null,
92
+ minimumVersion,
93
+ message,
94
+ guidance,
95
+ }) {
96
+ return {
97
+ ok: false,
98
+ code,
99
+ coreRoot,
100
+ installedVersion,
101
+ minimumVersion,
102
+ message,
103
+ guidance,
104
+ };
105
+ }
106
+
107
+ function readFrontmatterVersion(content) {
108
+ const match = content.match(/^version:\s*([^\s#]+)\s*(?:#.*)?$/m);
109
+ return match?.[1] ?? null;
110
+ }
111
+
112
+ function parseVersion(value) {
113
+ if (typeof value !== 'string') {
114
+ return null;
115
+ }
116
+ const match = value.match(
117
+ /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$/,
118
+ );
119
+ if (!match) {
120
+ return null;
121
+ }
122
+ return {
123
+ major: Number(match[1]),
124
+ minor: Number(match[2]),
125
+ patch: Number(match[3]),
126
+ };
127
+ }