@open-agent-toolkit/cli 0.2.66 → 0.2.67
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 +14 -1
- package/assets/docs/cli-utilities/project-log.md +63 -4
- package/assets/docs/contributing/code.md +8 -4
- package/assets/docs/contributing/skills.md +29 -17
- package/assets/docs/docs-tooling/commands.md +3 -1
- package/assets/docs/provider-sync/manifest-and-drift.md +2 -18
- package/assets/docs/workflows/projects/lifecycle.md +17 -0
- package/assets/docs/workflows/projects/picking-up-projects.md +18 -0
- package/assets/docs/workflows/skills/repo-improve.md +7 -3
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/create-agnostic-skill/SKILL.md +19 -37
- package/assets/skills/create-agnostic-skill/references/docs/skills-guide.md +58 -15
- package/assets/skills/create-agnostic-skill/references/skill-template.md +17 -15
- package/assets/skills/oat-agent-instructions-apply/SKILL.md +1 -1
- package/assets/skills/oat-agent-instructions-apply/references/instruction-file-templates/glob-scoped-rule.md +1 -4
- package/assets/skills/oat-doctor/SKILL.md +1 -4
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +1 -1
- package/assets/skills/oat-project-complete/SKILL.md +158 -30
- package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +26 -7
- package/assets/skills/oat-project-complete/scripts/validate-durable-archive-receipt.mjs +183 -0
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-retro/SKILL.md +1 -1
- package/assets/skills/oat-project-retro/references/apply-procedure.md +11 -0
- package/assets/skills/oat-project-review-provide/SKILL.md +4 -4
- package/assets/skills/oat-project-summary/SKILL.md +24 -2
- package/assets/skills/oat-repo-improve/SKILL.md +9 -2
- package/assets/skills/oat-repo-improve/references/plan-template.md +4 -6
- package/assets/skills/oat-repo-knowledge-index/SKILL.md +11 -9
- package/assets/skills/oat-wave-execute/SKILL.md +20 -6
- package/assets/skills/oat-wave-program/SKILL.md +9 -4
- package/dist/commands/config/index.d.ts +2 -1
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +133 -38
- package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/index.js +68 -12
- package/dist/commands/docs/init/index.d.ts +11 -0
- package/dist/commands/docs/init/index.d.ts.map +1 -1
- package/dist/commands/docs/init/index.js +19 -1
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +10 -2
- package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.utils.js +42 -9
- package/dist/commands/internal/validate-skill-version-bumps.d.ts.map +1 -1
- package/dist/commands/internal/validate-skill-version-bumps.js +5 -5
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +32 -18
- package/dist/commands/project/log/append.d.ts +48 -0
- package/dist/commands/project/log/append.d.ts.map +1 -1
- package/dist/commands/project/log/append.js +207 -30
- package/dist/commands/project/log/check.d.ts +87 -1
- package/dist/commands/project/log/check.d.ts.map +1 -1
- package/dist/commands/project/log/check.js +139 -5
- package/dist/commands/project/log/grammar.d.ts +165 -0
- package/dist/commands/project/log/grammar.d.ts.map +1 -1
- package/dist/commands/project/log/grammar.js +219 -1
- package/dist/commands/project/log/rollup.d.ts +16 -1
- package/dist/commands/project/log/rollup.d.ts.map +1 -1
- package/dist/commands/project/log/rollup.js +22 -2
- package/dist/commands/project/log/synthesize.d.ts +16 -0
- package/dist/commands/project/log/synthesize.d.ts.map +1 -1
- package/dist/commands/project/log/synthesize.js +85 -9
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +24 -2
- package/dist/commands/sync/apply.d.ts.map +1 -1
- package/dist/commands/sync/apply.js +6 -4
- package/dist/config/dispatch-matrix.d.ts.map +1 -1
- package/dist/config/dispatch-matrix.js +13 -5
- package/dist/config/oat-config.d.ts +15 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +61 -7
- package/dist/config/own-keys.d.ts +22 -0
- package/dist/config/own-keys.d.ts.map +1 -0
- package/dist/config/own-keys.js +32 -0
- package/dist/config/resolve.d.ts +11 -0
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +35 -11
- package/dist/config/sync-config.d.ts.map +1 -1
- package/dist/config/sync-config.js +16 -0
- package/dist/drift/detector.d.ts.map +1 -1
- package/dist/drift/detector.js +52 -1
- package/dist/drift/skill-view-diagnostic.d.ts.map +1 -1
- package/dist/drift/skill-view-diagnostic.js +2 -23
- package/dist/engine/compute-plan.d.ts.map +1 -1
- package/dist/engine/compute-plan.js +22 -72
- package/dist/engine/managed-copy-hash.d.ts +69 -0
- package/dist/engine/managed-copy-hash.d.ts.map +1 -0
- package/dist/engine/managed-copy-hash.js +175 -0
- package/dist/fs/assets.d.ts +11 -0
- package/dist/fs/assets.d.ts.map +1 -1
- package/dist/fs/assets.js +1 -1
- package/dist/manifest/hash.d.ts +110 -0
- package/dist/manifest/hash.d.ts.map +1 -1
- package/dist/manifest/hash.js +111 -13
- package/dist/providers/ceiling/registry.d.ts.map +1 -1
- package/dist/providers/ceiling/registry.js +6 -1
- package/dist/providers/identity/dispatch-report.d.ts.map +1 -1
- package/dist/providers/identity/dispatch-report.js +4 -1
- package/dist/release/public-package-contract.d.ts.map +1 -1
- package/dist/release/public-package-contract.js +21 -0
- package/dist/validation/skills.d.ts.map +1 -1
- package/dist/validation/skills.js +305 -40
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Task
|
|
8
8
|
metadata:
|
|
9
|
-
version: 1.9.
|
|
9
|
+
version: 1.9.2
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Execute a Wave of External Plans
|
|
@@ -184,6 +184,11 @@ in the Drift Refresh Record with pointer-only references elsewhere — a
|
|
|
184
184
|
reconciliation that waives a source-plan requirement is a plan-gate Important
|
|
185
185
|
(wave-4 evidence).
|
|
186
186
|
|
|
187
|
+
Recon is delegated; the Drift Refresh Record entries and any reconciliation
|
|
188
|
+
that amends a plan's mechanism — including after a tripped STOP — are plan
|
|
189
|
+
writes and stay on the orchestrator's own model class, the same rule
|
|
190
|
+
`oat-repo-improve` applies to external-plan writes.
|
|
191
|
+
|
|
187
192
|
### Step 3: Scaffold the wrapper project
|
|
188
193
|
|
|
189
194
|
1. Probe the installed scaffold interface first with `oat project new --help`.
|
|
@@ -380,8 +385,17 @@ archive anything first.
|
|
|
380
385
|
ledger, rolled into `summary.md` `## Workflow Observations`.
|
|
381
386
|
3. **Serialized backlog archival** — `oat backlog archive` with real summaries,
|
|
382
387
|
one commit.
|
|
383
|
-
4. **
|
|
384
|
-
|
|
388
|
+
4. **Closeout gate run** — after the archival commit, and after ANY later commit
|
|
389
|
+
that touches a gated surface (`.oat/repo/**`, `.agents/**`, `packages/**`,
|
|
390
|
+
`apps/**`), re-run the integration DoD gates with cache bypass. The last
|
|
391
|
+
content commit on the branch is ALWAYS gated. Step 1 certifies the tree it
|
|
392
|
+
ran on, not a commit that lands after it: in W7 the archival commit was the
|
|
393
|
+
only post-fan-in commit touching a gated surface, it left the suite red under
|
|
394
|
+
a shipped test that scans `.oat/repo`, and the root final review in step 5
|
|
395
|
+
then ran against a red tree. Bypass the cache and record `Cached: 0`; a
|
|
396
|
+
replayed result is evidence about an earlier tree, not this one.
|
|
397
|
+
5. **Root final review.**
|
|
398
|
+
6. **Cross-runtime final gate** — judgment-sweep dispositions; after every fix
|
|
385
399
|
disposition has its required stored verification record, flip the row to
|
|
386
400
|
`passed`. A final
|
|
387
401
|
gate MUST NOT remain at `fixes_completed`: `passed` is the only terminal
|
|
@@ -390,10 +404,10 @@ archive anything first.
|
|
|
390
404
|
the upstream stomp class was fixed in oat 0.1.65 and stoa's W6 supplied the
|
|
391
405
|
final clean observation (three gate rounds, zero stomps, watch never fired,
|
|
392
406
|
2026-07-20).
|
|
393
|
-
|
|
407
|
+
7. **Pre-approval sequence** per `workflow.postImplementSequence`, then a single
|
|
394
408
|
HiLL. File follow-up-ledger backlog items at closeout (on main post-merge, or
|
|
395
409
|
pre-gate if the operator prefers them in the PR).
|
|
396
|
-
|
|
410
|
+
8. **The full `oat-project-complete` PROCESS, with an explicit autonomous
|
|
397
411
|
deferral branch.** Interactive runs retain the standing per-wave order
|
|
398
412
|
review → complete → merge (an open PR is expected, not a blocker — the
|
|
399
413
|
archive-aware PR body sync handles it). The requirement remains the whole
|
|
@@ -424,7 +438,7 @@ archive anything first.
|
|
|
424
438
|
that execution occurs after the one human-gated program-end checkpoint in
|
|
425
439
|
`oat-wave-program`, across every deferred wave wrapper.
|
|
426
440
|
|
|
427
|
-
|
|
441
|
+
9. **After the operator merges:** reconcile (squash-merge means content-diff the
|
|
428
442
|
branch vs main; cherry-pick stragglers), reset the working branch, clean stale
|
|
429
443
|
phase branches, and run `oat-wave-program` `wave-close <wave-id>` so the
|
|
430
444
|
program ledger records the merge (PR, SHA, completion-record link) and flips
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Task
|
|
8
8
|
metadata:
|
|
9
|
-
version: 1.5.
|
|
9
|
+
version: 1.5.2
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Program a Corpus of External Plans into Waves
|
|
@@ -66,7 +66,12 @@ target (same disclaimer as the plan indexes).
|
|
|
66
66
|
token-cost requires W3 permission-policy").
|
|
67
67
|
- **Status ledger:** per wave — composed → in-progress (project link) → merged
|
|
68
68
|
(PR + merge SHA + completion record link). Updated by this skill's
|
|
69
|
-
`wave-close` mode, invoked from `oat-wave-execute` closeout step
|
|
69
|
+
`wave-close` mode, invoked from `oat-wave-execute` closeout step 9. Those
|
|
70
|
+
three are the complete ledger vocabulary, including at program close. `done`
|
|
71
|
+
is a wave-table plan-row status from the Wave table bullet above and is never
|
|
72
|
+
a ledger status. The executable owner of the ledger vocabulary is the
|
|
73
|
+
external-plan readiness contract, whose `WAVE_STATUSES` rejects any ledger
|
|
74
|
+
status outside it.
|
|
70
75
|
|
|
71
76
|
## Process
|
|
72
77
|
|
|
@@ -114,7 +119,7 @@ target (same disclaimer as the plan indexes).
|
|
|
114
119
|
program recap is not run, record `recap: not run — {reason}` there. Every
|
|
115
120
|
optional step gets an explicit disposition; silence is indistinguishable
|
|
116
121
|
from oversight (Orc program-recap evidence).
|
|
117
|
-
6. When the final wave's ledger row flips to `
|
|
122
|
+
6. When the final wave's ledger row flips to `merged` and all wave merges are
|
|
118
123
|
recorded, stop at exactly one HUMAN-GATED program completion checkpoint,
|
|
119
124
|
including in autonomous runs:
|
|
120
125
|
"All waves are merged and the program is complete. Run the completion tail
|
|
@@ -169,7 +174,7 @@ Publishing is human-gated; this caller never invokes publish.
|
|
|
169
174
|
|
|
170
175
|
- `oat-wave-execute` **Inputs** resolves its lane list from this artifact (falling
|
|
171
176
|
back to index wave hints only when no program artifact exists).
|
|
172
|
-
- `oat-wave-execute` closeout step
|
|
177
|
+
- `oat-wave-execute` closeout step 9 invokes `wave-close` here.
|
|
173
178
|
- A lane parked mid-wave (source-plan STOP) surfaces in BOTH artifacts: the
|
|
174
179
|
wave project's blocker record and this ledger's `deferred` row.
|
|
175
180
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildCommandContext, type CommandContext } from '../../app/command-context.js';
|
|
2
2
|
import { type PromptContext } from '../shared/shared.prompts.js';
|
|
3
|
-
import { type OatConfig, type OatLocalConfig, type UserConfig } from '../../config/oat-config.js';
|
|
3
|
+
import { type OatConfig, type OatConfigRead, type OatLocalConfig, type UserConfig } from '../../config/oat-config.js';
|
|
4
4
|
import { type ResolvedConfig } from '../../config/resolve.js';
|
|
5
5
|
import { type MatrixCellAvailabilityResponse, type ValidateMatrixCellOptions } from '../../providers/identity/availability.js';
|
|
6
6
|
import { createDispatchValidationPassContext, validateDispatchMatrixRefs } from '../../providers/identity/dispatch-validation.js';
|
|
@@ -12,6 +12,7 @@ interface ConfigCommandDependencies {
|
|
|
12
12
|
readOatConfigForDefaultScopeRepair: (repoRoot: string) => Promise<OatConfig>;
|
|
13
13
|
readOatConfigForDocumentationExcludesRepair: (repoRoot: string) => Promise<OatConfig>;
|
|
14
14
|
readOatConfigForInstructionPointerExcludesRepair: (repoRoot: string) => Promise<OatConfig>;
|
|
15
|
+
readOatConfigWithWarnings: (repoRoot: string) => Promise<OatConfigRead>;
|
|
15
16
|
writeOatConfig: (repoRoot: string, config: OatConfig) => Promise<void>;
|
|
16
17
|
readOatLocalConfig: (repoRoot: string) => Promise<OatLocalConfig>;
|
|
17
18
|
writeOatLocalConfig: (repoRoot: string, config: OatLocalConfig) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAIhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAoBzC,OAAO,EAML,KAAK,SAAS,EACd,KAAK,cAAc,EAQnB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,8 +11,9 @@ import { normalizeDispatchMatrix, validateDispatchRouteTarget, walkDispatchMatri
|
|
|
11
11
|
import { formatDispatchNotices, terminalReviewerNoticesForMatrix, } from '../../config/dispatch-notices.js';
|
|
12
12
|
import { dispatchPolicyModeDescription, dispatchPolicyPolicyDescription, managedDispatchPolicyValueList, } from '../../config/dispatch-policy-options.js';
|
|
13
13
|
import { parseJsonConfig } from '../../config/json.js';
|
|
14
|
-
import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatConfigForDefaultScopeRepair, readOatConfigForDocumentationExcludesRepair, readOatConfigForInstructionPointerExcludesRepair, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
15
|
-
import {
|
|
14
|
+
import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatConfigForDefaultScopeRepair, readOatConfigForDocumentationExcludesRepair, readOatConfigForInstructionPointerExcludesRepair, readOatConfigWithWarnings, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
15
|
+
import { getOwnKey, setOwnKey } from '../../config/own-keys.js';
|
|
16
|
+
import { resolveEffectiveConfig, resolveEnvOverride, } from '../../config/resolve.js';
|
|
16
17
|
import { resolveAssetsRoot } from '../../fs/assets.js';
|
|
17
18
|
import { atomicWriteJson } from '../../fs/io.js';
|
|
18
19
|
import { resolveProjectRoot } from '../../fs/paths.js';
|
|
@@ -949,6 +950,7 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
949
950
|
readOatConfigForDefaultScopeRepair,
|
|
950
951
|
readOatConfigForDocumentationExcludesRepair,
|
|
951
952
|
readOatConfigForInstructionPointerExcludesRepair,
|
|
953
|
+
readOatConfigWithWarnings,
|
|
952
954
|
writeOatConfig,
|
|
953
955
|
readOatLocalConfig,
|
|
954
956
|
writeOatLocalConfig,
|
|
@@ -1258,9 +1260,16 @@ function defaultSurfaceForKey(key) {
|
|
|
1258
1260
|
/**
|
|
1259
1261
|
* Resolve the `--shared` / `--local` / `--user` trio to a single surface.
|
|
1260
1262
|
*
|
|
1261
|
-
*
|
|
1262
|
-
* mutual-exclusion rule
|
|
1263
|
-
*
|
|
1263
|
+
* `set`, `unset`, and `adopt` all resolve the trio here -- this is the only
|
|
1264
|
+
* copy of the mutual-exclusion rule and of its message. It was lifted verbatim
|
|
1265
|
+
* out of the `set` action so `unset` could share it, and `adopt` was folded on
|
|
1266
|
+
* afterwards.
|
|
1267
|
+
*
|
|
1268
|
+
* The message is pinned in two ways, so changing it means moving all three
|
|
1269
|
+
* commands together: the per-command cases assert it individually, and the
|
|
1270
|
+
* three-command parity case in `index.test.ts` ('set, unset, and adopt reject
|
|
1271
|
+
* the same conflicting surface flags with one message') compares the three
|
|
1272
|
+
* commands' output to each other as well as to the literal.
|
|
1264
1273
|
*/
|
|
1265
1274
|
function resolveSurfaceFlags(options) {
|
|
1266
1275
|
const flagsPresent = [options.shared, options.local, options.user].filter(Boolean).length;
|
|
@@ -1510,7 +1519,10 @@ function applyWorkflowValue(workflow, key, value) {
|
|
|
1510
1519
|
const { provider, tier } = parseDispatchCeilingProviderConfigKey(key);
|
|
1511
1520
|
const providers = workflow.dispatchCeiling?.providers ?? {};
|
|
1512
1521
|
if (tier) {
|
|
1513
|
-
|
|
1522
|
+
// `provider` is a user-supplied config-key segment, so the lookup is
|
|
1523
|
+
// own-key guarded. The computed-key writes below use object-literal
|
|
1524
|
+
// define semantics and are already safe.
|
|
1525
|
+
const existingProviderValue = getOwnKey(providers, provider);
|
|
1514
1526
|
const existingTierMap = existingProviderValue &&
|
|
1515
1527
|
typeof existingProviderValue === 'object' &&
|
|
1516
1528
|
!Array.isArray(existingProviderValue)
|
|
@@ -1736,20 +1748,24 @@ function buildResolvedConfigAggregate(resolved, key) {
|
|
|
1736
1748
|
if (sourceRank[entry.source] > sourceRank[source]) {
|
|
1737
1749
|
source = entry.source;
|
|
1738
1750
|
}
|
|
1751
|
+
// Key segments come from resolved config keys, so a provider literally
|
|
1752
|
+
// named `__proto__` reaches this walk. A bare `cursor[part]` descends into
|
|
1753
|
+
// `Object.prototype` and the leaf write then lands on the global
|
|
1754
|
+
// prototype, polluting every object in the process -- not just this map.
|
|
1739
1755
|
const parts = entryKey.slice(prefix.length).split('.');
|
|
1740
1756
|
let cursor = value;
|
|
1741
1757
|
for (const [index, part] of parts.entries()) {
|
|
1742
1758
|
if (index === parts.length - 1) {
|
|
1743
|
-
cursor
|
|
1759
|
+
setOwnKey(cursor, part, entry.value);
|
|
1744
1760
|
}
|
|
1745
1761
|
else {
|
|
1746
|
-
const nested = cursor
|
|
1762
|
+
const nested = getOwnKey(cursor, part);
|
|
1747
1763
|
if (typeof nested !== 'object' ||
|
|
1748
1764
|
nested === null ||
|
|
1749
1765
|
Array.isArray(nested)) {
|
|
1750
|
-
cursor
|
|
1766
|
+
setOwnKey(cursor, part, {});
|
|
1751
1767
|
}
|
|
1752
|
-
cursor = cursor
|
|
1768
|
+
cursor = getOwnKey(cursor, part);
|
|
1753
1769
|
}
|
|
1754
1770
|
}
|
|
1755
1771
|
}
|
|
@@ -2214,8 +2230,16 @@ async function unsetConfigValue(repoRoot, userConfigDir, key, surface, dependenc
|
|
|
2214
2230
|
key === 'pjm.remote.schemaVersion') {
|
|
2215
2231
|
throw new Error(`Config key '${key}' is read-only; unset one of its documented child keys.`);
|
|
2216
2232
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2233
|
+
// Deliberately not `resolveEffectiveConfig`: its result was used for this one
|
|
2234
|
+
// boolean, and its strict normalization throws on exactly the malformed
|
|
2235
|
+
// stored value `unset` exists to remove. `ENV_OVERRIDE_MAP` covers three
|
|
2236
|
+
// keys, all of which are in `DEFAULT_SHARED_CONFIG` and therefore always
|
|
2237
|
+
// present in the resolved view, and the env branch is checked first there --
|
|
2238
|
+
// so this probe answers identically for every config key. The whole-config
|
|
2239
|
+
// read this replaces was also a validation barrier; that role is reinstated
|
|
2240
|
+
// separately by the targeted barrier below, which reads every surface except
|
|
2241
|
+
// the targeted one (the targeted one is re-normalized on write instead).
|
|
2242
|
+
const envShadowed = resolveEnvOverride(key, dependencies.processEnv) !== undefined;
|
|
2219
2243
|
if (key === 'activeProject' || key === 'lastPausedProject') {
|
|
2220
2244
|
throw new Error(`Cannot unset state key '${key}'. Lifecycle state is cleared with: oat config set ${key} ''`);
|
|
2221
2245
|
}
|
|
@@ -2236,6 +2260,44 @@ async function unsetConfigValue(repoRoot, userConfigDir, key, surface, dependenc
|
|
|
2236
2260
|
? defaultSurfaceForKey(key)
|
|
2237
2261
|
: surface;
|
|
2238
2262
|
const path = configPathForKey(key);
|
|
2263
|
+
// Targeted strict barrier.
|
|
2264
|
+
//
|
|
2265
|
+
// `resolveEffectiveConfig` used to run at the top of this function, and as a
|
|
2266
|
+
// side effect of resolving it strictly read all three surfaces. That read --
|
|
2267
|
+
// not its return value -- is what made a malformed value anywhere abort
|
|
2268
|
+
// `unset` before it wrote anything. The `envShadowed` probe above no longer
|
|
2269
|
+
// performs it, so the barrier is reinstated here explicitly and narrowly:
|
|
2270
|
+
// every surface OTHER than the targeted one is read strictly, through the
|
|
2271
|
+
// same injected readers `resolveEffectiveConfig` composes, so a malformed
|
|
2272
|
+
// value on an untargeted surface still aborts with the identical
|
|
2273
|
+
// `Invalid <key>` message it produced before.
|
|
2274
|
+
//
|
|
2275
|
+
// The targeted surface is deliberately excluded, and that exclusion is the
|
|
2276
|
+
// whole point of the change: `removeFromSurface` reads it through the
|
|
2277
|
+
// key-specific repair reader -- lenient only for the key being removed -- and
|
|
2278
|
+
// writes it back through `writeOatConfig` / `writeOatLocalConfig` /
|
|
2279
|
+
// `writeUserConfig`, every one of which re-normalizes. A malformed value on
|
|
2280
|
+
// the targeted surface that is NOT the key being removed is therefore still
|
|
2281
|
+
// refused, on the rewrite rather than on a pre-read.
|
|
2282
|
+
if (effectiveSurface !== 'shared') {
|
|
2283
|
+
await dependencies.readOatConfig(repoRoot);
|
|
2284
|
+
}
|
|
2285
|
+
if (effectiveSurface !== 'local') {
|
|
2286
|
+
await dependencies.readOatLocalConfig(repoRoot);
|
|
2287
|
+
}
|
|
2288
|
+
if (effectiveSurface !== 'user') {
|
|
2289
|
+
await dependencies.readUserConfig(userConfigDir);
|
|
2290
|
+
}
|
|
2291
|
+
// The one branch that gets no re-normalization on the way out: the
|
|
2292
|
+
// `pjm.remote` raw-write path in `removeFromSurface` persists through
|
|
2293
|
+
// `atomicWriteJson`, bypassing `writeOatConfig`. So the targeted shared
|
|
2294
|
+
// surface must be read strictly here too. `pjm.remote` children are not among
|
|
2295
|
+
// the malformed-value repair keys this change targets, which is why adding
|
|
2296
|
+
// this read keeps that branch's acceptance set byte-identical to its previous
|
|
2297
|
+
// one rather than narrowing it.
|
|
2298
|
+
if (isPjmRemoteConfigKey(key)) {
|
|
2299
|
+
await dependencies.readOatConfig(repoRoot);
|
|
2300
|
+
}
|
|
2239
2301
|
const removed = await removeFromSurface(repoRoot, userConfigDir, key, effectiveSurface, path, dependencies);
|
|
2240
2302
|
// The env guard is scoped to the targeted surface, not to the effective
|
|
2241
2303
|
// value, so that `unset` mirrors `set`: `set` happily rewrites a stored value
|
|
@@ -2302,9 +2364,12 @@ async function removeFromSurface(repoRoot, userConfigDir, key, effectiveSurface,
|
|
|
2302
2364
|
// policy.description=none and authority.default=read-only. Removing a
|
|
2303
2365
|
// leaf from that normalized object and sending it through writeOatConfig
|
|
2304
2366
|
// would therefore write the leaf straight back while reporting success.
|
|
2305
|
-
// The
|
|
2306
|
-
//
|
|
2307
|
-
//
|
|
2367
|
+
// The strict shared read happens in `unsetConfigValue`'s targeted barrier
|
|
2368
|
+
// immediately before this call -- this branch depends on it, because
|
|
2369
|
+
// `atomicWriteJson` below bypasses `writeOatConfig`'s normalization, so
|
|
2370
|
+
// nothing downstream would catch a malformed document. Given that read,
|
|
2371
|
+
// perform this removal against the validated raw document and atomically
|
|
2372
|
+
// persist only the requested structural change.
|
|
2308
2373
|
const repaired = await removeConfigPathOnDisk(configPath, path);
|
|
2309
2374
|
if (!repaired) {
|
|
2310
2375
|
return false;
|
|
@@ -2424,15 +2489,22 @@ function applyDispatchMatrixRecommendation(workflow, recommendation) {
|
|
|
2424
2489
|
const providers = {
|
|
2425
2490
|
...recommendation.providers,
|
|
2426
2491
|
};
|
|
2492
|
+
// `existingProviders` is a `normalizeDispatchMatrix` product, which now
|
|
2493
|
+
// keeps a provider literally named `__proto__` as an own key, so
|
|
2494
|
+
// `Object.entries` yields it here and every access is own-key guarded. The
|
|
2495
|
+
// `{ ...recommendation.providers }` spread above is already safe.
|
|
2427
2496
|
for (const [provider, existingValue] of Object.entries(existingProviders)) {
|
|
2428
|
-
const recommendedValue = recommendation.providers
|
|
2497
|
+
const recommendedValue = getOwnKey(recommendation.providers, provider);
|
|
2429
2498
|
if (recommendedValue &&
|
|
2430
2499
|
typeof recommendedValue !== 'string' &&
|
|
2431
2500
|
typeof existingValue !== 'string') {
|
|
2432
|
-
providers
|
|
2501
|
+
setOwnKey(providers, provider, {
|
|
2502
|
+
...recommendedValue,
|
|
2503
|
+
...existingValue,
|
|
2504
|
+
});
|
|
2433
2505
|
}
|
|
2434
2506
|
else {
|
|
2435
|
-
providers
|
|
2507
|
+
setOwnKey(providers, provider, existingValue);
|
|
2436
2508
|
}
|
|
2437
2509
|
}
|
|
2438
2510
|
return {
|
|
@@ -2449,11 +2521,10 @@ async function effectiveTerminalReviewerNotices(repoRoot, userConfigDir, depende
|
|
|
2449
2521
|
const effectiveProviders = {};
|
|
2450
2522
|
for (const config of [resolved.user, resolved.shared, resolved.local]) {
|
|
2451
2523
|
for (const [provider, value] of Object.entries(config.workflow?.dispatchCeiling?.providers ?? {})) {
|
|
2452
|
-
const existing = effectiveProviders
|
|
2453
|
-
effectiveProviders
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
: value;
|
|
2524
|
+
const existing = getOwnKey(effectiveProviders, provider);
|
|
2525
|
+
setOwnKey(effectiveProviders, provider, isRecord(existing) && isRecord(value)
|
|
2526
|
+
? { ...existing, ...value }
|
|
2527
|
+
: value);
|
|
2457
2528
|
}
|
|
2458
2529
|
}
|
|
2459
2530
|
return terminalReviewerNoticesForMatrix(effectiveProviders);
|
|
@@ -2560,6 +2631,32 @@ function formatCatalogDetails(entries) {
|
|
|
2560
2631
|
].join('\n'))
|
|
2561
2632
|
.join('\n\n');
|
|
2562
2633
|
}
|
|
2634
|
+
/**
|
|
2635
|
+
* The `warnings` field for a JSON document, omitted when there is nothing to
|
|
2636
|
+
* say.
|
|
2637
|
+
*
|
|
2638
|
+
* An always-present `warnings: []` would be new noise on the common path and
|
|
2639
|
+
* would break every existing exact-match assertion on these documents, so the
|
|
2640
|
+
* key appears only when it carries something -- the same shape
|
|
2641
|
+
* `exclusionWarnings` uses in `commands/instructions/sync/sync.ts`.
|
|
2642
|
+
*/
|
|
2643
|
+
function jsonWarnings(warnings) {
|
|
2644
|
+
return warnings.length > 0 ? { warnings } : {};
|
|
2645
|
+
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Emit shared-config read warnings on the human channel.
|
|
2648
|
+
*
|
|
2649
|
+
* `logger.warn` is a no-op under `--json` (`ui/logger.ts`), so the JSON
|
|
2650
|
+
* document carries the same strings in its own `warnings` field instead. Each
|
|
2651
|
+
* caller picks exactly one channel, which is why this is only ever reached from
|
|
2652
|
+
* the non-JSON branch: routing both ways would print nothing extra today but
|
|
2653
|
+
* would double-print the moment the logger learned to emit under `--json`.
|
|
2654
|
+
*/
|
|
2655
|
+
function emitConfigReadWarnings(context, warnings) {
|
|
2656
|
+
for (const warning of warnings) {
|
|
2657
|
+
context.logger.warn(warning);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2563
2660
|
async function runGet(keyArg, context, dependencies) {
|
|
2564
2661
|
try {
|
|
2565
2662
|
if (!isConfigKey(keyArg)) {
|
|
@@ -2567,14 +2664,21 @@ async function runGet(keyArg, context, dependencies) {
|
|
|
2567
2664
|
}
|
|
2568
2665
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
2569
2666
|
const userConfigDir = join(context.home, '.oat');
|
|
2667
|
+
// One read per invocation, not one per resolved layer, so a wrong-typed
|
|
2668
|
+
// key is reported once. A fail-closed key throws here exactly as it would
|
|
2669
|
+
// have thrown inside `resolveEffectiveConfig` below -- same normalizer,
|
|
2670
|
+
// same message -- so error precedence is unchanged.
|
|
2671
|
+
const { warnings } = await dependencies.readOatConfigWithWarnings(repoRoot);
|
|
2570
2672
|
const value = await getConfigValue(repoRoot, userConfigDir, keyArg, dependencies, context.json);
|
|
2571
2673
|
if (context.json) {
|
|
2572
2674
|
context.logger.json({
|
|
2573
2675
|
status: 'ok',
|
|
2574
2676
|
...value,
|
|
2677
|
+
...jsonWarnings(warnings),
|
|
2575
2678
|
});
|
|
2576
2679
|
}
|
|
2577
2680
|
else {
|
|
2681
|
+
emitConfigReadWarnings(context, warnings);
|
|
2578
2682
|
context.logger.info(formatResolvedValue(value.value) ?? '');
|
|
2579
2683
|
}
|
|
2580
2684
|
process.exitCode = 0;
|
|
@@ -2694,6 +2798,9 @@ async function runList(context, dependencies) {
|
|
|
2694
2798
|
try {
|
|
2695
2799
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
2696
2800
|
const userConfigDir = join(context.home, '.oat');
|
|
2801
|
+
// `list` resolves every key and each resolution re-reads the shared file,
|
|
2802
|
+
// so the warning has to come from this one read rather than from the loop.
|
|
2803
|
+
const { warnings } = await dependencies.readOatConfigWithWarnings(repoRoot);
|
|
2697
2804
|
const values = [];
|
|
2698
2805
|
for (const key of await listConfigKeys(repoRoot, userConfigDir, dependencies)) {
|
|
2699
2806
|
values.push(await getConfigValue(repoRoot, userConfigDir, key, dependencies));
|
|
@@ -2702,9 +2809,11 @@ async function runList(context, dependencies) {
|
|
|
2702
2809
|
context.logger.json({
|
|
2703
2810
|
status: 'ok',
|
|
2704
2811
|
values,
|
|
2812
|
+
...jsonWarnings(warnings),
|
|
2705
2813
|
});
|
|
2706
2814
|
}
|
|
2707
2815
|
else {
|
|
2816
|
+
emitConfigReadWarnings(context, warnings);
|
|
2708
2817
|
context.logger.info(formatList(values));
|
|
2709
2818
|
}
|
|
2710
2819
|
process.exitCode = 0;
|
|
@@ -2825,21 +2934,7 @@ export function createConfigCommand(overrides = {}) {
|
|
|
2825
2934
|
.action(async (template, options, command) => {
|
|
2826
2935
|
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
2827
2936
|
try {
|
|
2828
|
-
const
|
|
2829
|
-
options.shared,
|
|
2830
|
-
options.local,
|
|
2831
|
-
options.user,
|
|
2832
|
-
].filter(Boolean).length;
|
|
2833
|
-
if (flagsPresent > 1) {
|
|
2834
|
-
throw new Error('--shared, --local, and --user flags are mutually exclusive; pass at most one.');
|
|
2835
|
-
}
|
|
2836
|
-
let surface = 'auto';
|
|
2837
|
-
if (options.shared)
|
|
2838
|
-
surface = 'shared';
|
|
2839
|
-
else if (options.local)
|
|
2840
|
-
surface = 'local';
|
|
2841
|
-
else if (options.user)
|
|
2842
|
-
surface = 'user';
|
|
2937
|
+
const surface = resolveSurfaceFlags(options);
|
|
2843
2938
|
await runAdopt(template, { surface }, context, dependencies);
|
|
2844
2939
|
}
|
|
2845
2940
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAI5C,eAAO,MAAM,uBAAuB,iIAC4F,CAAC;AAuBjI,UAAU,6BAA6B;IACrC,aAAa,EAAE,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,aAAa,EAAE,oBAAoB,KAChD,OAAO,CAAC,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC,CAAC;IACjD,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,KAAK,MAAM,CAAC;IACrE,SAAS,EAAE,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7D;AAED,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,QAAQ,EAAE,6BAA6B,CAAC;CACzC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAI5C,eAAO,MAAM,uBAAuB,iIAC4F,CAAC;AAuBjI,UAAU,6BAA6B;IACrC,aAAa,EAAE,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,aAAa,EAAE,oBAAoB,KAChD,OAAO,CAAC,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC,CAAC;IACjD,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,KAAK,MAAM,CAAC;IACrE,SAAS,EAAE,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7D;AAED,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,QAAQ,EAAE,6BAA6B,CAAC;CACzC;AAomBD,wBAAgB,8BAA8B,CAC5C,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAgCT"}
|
|
@@ -94,6 +94,28 @@ function isInside(parent, candidate) {
|
|
|
94
94
|
* dangling-link hops resolved below.
|
|
95
95
|
*/
|
|
96
96
|
const MAX_SYMLINK_HOPS = 32;
|
|
97
|
+
/**
|
|
98
|
+
* The hop-cap refusal, addressed to whoever owns the offending path.
|
|
99
|
+
*
|
|
100
|
+
* A derived docs directory is unusable path *configuration*, so it takes
|
|
101
|
+
* `CONFIGURATION_EXIT_CODE` and the `documentation.root` repair, exactly like
|
|
102
|
+
* `missingRootError` and the "not a directory" refusal. An explicitly supplied
|
|
103
|
+
* path stays an actionable flag error.
|
|
104
|
+
*/
|
|
105
|
+
function hopCapError(context) {
|
|
106
|
+
const preamble = `Refusing to resolve ${context.origin}: its symlink chain exceeds ` +
|
|
107
|
+
`${MAX_SYMLINK_HOPS} hops. `;
|
|
108
|
+
if (context.role.kind === 'docs-dir') {
|
|
109
|
+
if (context.role.derived) {
|
|
110
|
+
return new CliError(`${preamble}The docs directory is derived from \`documentation.root\` ` +
|
|
111
|
+
'in .oat/config.json. Repair it with ' +
|
|
112
|
+
'`oat config set documentation.root <path>`, or pass --docs-dir with a ' +
|
|
113
|
+
'path that is not a deep symlink chain.', CONFIGURATION_EXIT_CODE);
|
|
114
|
+
}
|
|
115
|
+
return new CliError(`${preamble}Pass --docs-dir with a path that is not a deep symlink chain.`, REFUSAL_EXIT_CODE);
|
|
116
|
+
}
|
|
117
|
+
return new CliError(`${preamble}Pass --output with a path that is not a deep symlink chain.`, REFUSAL_EXIT_CODE);
|
|
118
|
+
}
|
|
97
119
|
/**
|
|
98
120
|
* Absolute path with symlinks resolved, so a symlinked parent cannot smuggle a
|
|
99
121
|
* write into a protected tree past the lexical containment checks.
|
|
@@ -105,7 +127,7 @@ const MAX_SYMLINK_HOPS = 32;
|
|
|
105
127
|
* relative link destination is joined to the parent's *real* location rather
|
|
106
128
|
* than its lexical spelling.
|
|
107
129
|
*/
|
|
108
|
-
async function canonicalize(target, deps, linkHops = 0) {
|
|
130
|
+
async function canonicalize(target, deps, context, linkHops = 0) {
|
|
109
131
|
const absolute = resolve(target);
|
|
110
132
|
try {
|
|
111
133
|
return await deps.realpath(absolute);
|
|
@@ -120,7 +142,7 @@ async function canonicalize(target, deps, linkHops = 0) {
|
|
|
120
142
|
if (parent === absolute) {
|
|
121
143
|
return absolute;
|
|
122
144
|
}
|
|
123
|
-
const canonicalParent = await canonicalize(parent, deps, linkHops);
|
|
145
|
+
const canonicalParent = await canonicalize(parent, deps, context, linkHops);
|
|
124
146
|
const candidate = join(canonicalParent, basename(absolute));
|
|
125
147
|
const linkTarget = await deps.readLinkIfSymlink(candidate);
|
|
126
148
|
if (linkTarget === null) {
|
|
@@ -128,10 +150,9 @@ async function canonicalize(target, deps, linkHops = 0) {
|
|
|
128
150
|
}
|
|
129
151
|
if (linkHops >= MAX_SYMLINK_HOPS) {
|
|
130
152
|
// Fail closed: falling through would hand the writer an unresolved link.
|
|
131
|
-
throw
|
|
132
|
-
'Pass --output with a path that is not a deep symlink chain.', REFUSAL_EXIT_CODE);
|
|
153
|
+
throw hopCapError(context);
|
|
133
154
|
}
|
|
134
|
-
return canonicalize(resolve(canonicalParent, linkTarget), deps, linkHops + 1);
|
|
155
|
+
return canonicalize(resolve(canonicalParent, linkTarget), deps, context, linkHops + 1);
|
|
135
156
|
}
|
|
136
157
|
function trimmed(value) {
|
|
137
158
|
const trimmedValue = value?.trim();
|
|
@@ -229,9 +250,9 @@ async function resolveIndexGeneratePaths(context, options, deps) {
|
|
|
229
250
|
};
|
|
230
251
|
}
|
|
231
252
|
/** `canonicalize`, but an unresolvable path yields null instead of throwing. */
|
|
232
|
-
async function canonicalizeOrNull(target, deps) {
|
|
253
|
+
async function canonicalizeOrNull(target, deps, role) {
|
|
233
254
|
try {
|
|
234
|
-
return await canonicalize(target, deps);
|
|
255
|
+
return await canonicalize(target, deps, { role, origin: target });
|
|
235
256
|
}
|
|
236
257
|
catch {
|
|
237
258
|
return null;
|
|
@@ -242,20 +263,34 @@ async function canonicalizeIndexGeneratePaths(resolved, deps) {
|
|
|
242
263
|
const lexicalDocumentationConfig = documentationConfigValue
|
|
243
264
|
? resolve(resolved.repoRoot, documentationConfigValue)
|
|
244
265
|
: null;
|
|
266
|
+
// Resolution order is load-bearing: the docs directory is canonicalized
|
|
267
|
+
// first, and the safety checks below compare against its canonical form.
|
|
245
268
|
return {
|
|
246
|
-
docsDir: await canonicalize(resolved.docsDir, deps
|
|
247
|
-
|
|
269
|
+
docsDir: await canonicalize(resolved.docsDir, deps, {
|
|
270
|
+
role: {
|
|
271
|
+
kind: 'docs-dir',
|
|
272
|
+
derived: resolved.docsDirSource !== 'flag',
|
|
273
|
+
},
|
|
274
|
+
origin: resolved.docsDir,
|
|
275
|
+
}),
|
|
276
|
+
outputPath: await canonicalize(resolved.outputPath, deps, {
|
|
277
|
+
role: { kind: 'output' },
|
|
278
|
+
origin: resolved.outputPath,
|
|
279
|
+
}),
|
|
248
280
|
// An unusable configured root only makes the config write ineligible; it
|
|
249
281
|
// must not fail a run whose paths were both supplied explicitly.
|
|
250
282
|
configuredRoot: resolved.configuredRoot
|
|
251
|
-
? await canonicalizeOrNull(resolved.configuredRoot, deps
|
|
283
|
+
? await canonicalizeOrNull(resolved.configuredRoot, deps, {
|
|
284
|
+
kind: 'other',
|
|
285
|
+
})
|
|
252
286
|
: null,
|
|
253
287
|
lexicalDocumentationConfig,
|
|
254
288
|
// Falling back to the lexical path keeps the refusal guard intact when the
|
|
255
289
|
// configured tool config cannot be canonicalized.
|
|
256
290
|
documentationConfig: lexicalDocumentationConfig
|
|
257
|
-
? ((await canonicalizeOrNull(lexicalDocumentationConfig, deps
|
|
258
|
-
|
|
291
|
+
? ((await canonicalizeOrNull(lexicalDocumentationConfig, deps, {
|
|
292
|
+
kind: 'other',
|
|
293
|
+
})) ?? lexicalDocumentationConfig)
|
|
259
294
|
: null,
|
|
260
295
|
};
|
|
261
296
|
}
|
|
@@ -364,11 +399,32 @@ async function runIndexGenerate(context, options, deps) {
|
|
|
364
399
|
entriesGenerated: entries.length,
|
|
365
400
|
docsDir,
|
|
366
401
|
docsDirSource: resolved.docsDirSource,
|
|
402
|
+
// Exactly the list handed to `deps.generateIndex` above, so a missing
|
|
403
|
+
// page can be attributed without re-deriving the merge.
|
|
404
|
+
excludes: resolved.excludes,
|
|
367
405
|
outputPath,
|
|
368
406
|
});
|
|
369
407
|
return;
|
|
370
408
|
}
|
|
371
409
|
context.logger.info(`Generated index with ${entries.length} entries from ${docsDir} → ${outputPath}`);
|
|
410
|
+
// A bare `0 entries` reads identically for an empty tree, a wrong
|
|
411
|
+
// `--docs-dir`, and exclusions that swallowed everything. Say which of those
|
|
412
|
+
// the operator should look at. This reports; it never changes the exit code
|
|
413
|
+
// or the written file.
|
|
414
|
+
//
|
|
415
|
+
// The wording is deliberately observational. This function sees indexed
|
|
416
|
+
// entries, never pattern matches, so it cannot know an exclusion actually
|
|
417
|
+
// matched anything: an empty tree with a pattern that matched nothing lands
|
|
418
|
+
// here too, and blaming the exclusions outright would be false in exactly
|
|
419
|
+
// that case.
|
|
420
|
+
if (entries.length === 0) {
|
|
421
|
+
context.logger.info(resolved.excludes.length > 0
|
|
422
|
+
? `No pages were indexed, with ${resolved.excludes.length} exclusion ` +
|
|
423
|
+
`pattern${resolved.excludes.length === 1 ? '' : 's'} active ` +
|
|
424
|
+
`(${resolved.excludes.join(', ')}). Check whether ` +
|
|
425
|
+
'`documentation.excludes` or --exclude is hiding the pages you expect.'
|
|
426
|
+
: `No pages were indexed: ${docsDir} contained no indexable pages.`);
|
|
427
|
+
}
|
|
372
428
|
}
|
|
373
429
|
async function runIndexGenerateCommand(context, options, dependencies) {
|
|
374
430
|
try {
|
|
@@ -21,6 +21,17 @@ interface DocsInitDependencies {
|
|
|
21
21
|
readOatConfig: (repoRoot: string) => Promise<OatConfig>;
|
|
22
22
|
confirmAction: (message: string, ctx: PromptContext) => Promise<boolean>;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The AGENTS.md `## Documentation` section for a freshly scaffolded app.
|
|
26
|
+
*
|
|
27
|
+
* Each bullet names the file that owns one role, matching what
|
|
28
|
+
* `buildDocumentationConfig` seeds into `documentation.*`. A Fumadocs app has
|
|
29
|
+
* two index files -- the authored source page under `docs/` and the app-root
|
|
30
|
+
* manifest `oat docs generate-index` writes -- and `documentation.index` names
|
|
31
|
+
* the second, so a single "Index file" bullet pointing at the first documented
|
|
32
|
+
* a file the config does not name. MkDocs has no generated manifest: its
|
|
33
|
+
* `documentation.index` is the nav YAML the Config bullet already names.
|
|
34
|
+
*/
|
|
24
35
|
export declare function buildDocsSectionBody(options: DocsInitResolvedOptions): string;
|
|
25
36
|
export declare function createDocsInitCommand(overrides?: Partial<DocsInitDependencies>): Command;
|
|
26
37
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,mBAAmB,EAIzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,YAAY,EAElB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAG5C,OAAO,EAIL,KAAK,uBAAuB,EAK7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,gBAAgB,CAAC;AAexB,UAAU,uBAAuB;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,sBAAsB,CAAC;CAC1C;AAED,UAAU,oBAAoB;IAC5B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;IAC9E,gBAAgB,EAAE,CAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,eAAe,EAAE,CAAC,CAAC,SAAS,MAAM,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAC1B,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvB,WAAW,EAAE,CACX,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,uBAAuB,EAChC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;IAC7C,qBAAqB,EAAE,CACrB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1E;AAqGD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAuB7E;AA6JD,wBAAgB,qBAAqB,CACnC,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CA6CT"}
|