@codex-agent/cli 0.1.0-main.12.sha31ff9e5 → 0.1.0-main.13.sha3ad2ccf
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/README.md +2 -1
- package/dist/codex-agent.mjs +562 -110
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ npx --yes @codex-agent/cli@latest context init --json
|
|
|
9
9
|
npx --yes @codex-agent/cli@latest context refresh --json
|
|
10
10
|
npx --yes @codex-agent/cli@latest doctor --json
|
|
11
11
|
npx --yes @codex-agent/cli@latest context save --proposal context-proposal.json --json
|
|
12
|
+
npx --yes @codex-agent/cli@latest context lint --json
|
|
12
13
|
npx --yes @codex-agent/cli@latest migrate navigation --from /path/to/project --json
|
|
13
14
|
npx --yes @codex-agent/cli@latest eval --json
|
|
14
15
|
```
|
|
@@ -17,4 +18,4 @@ Run these commands from the target repository. Use `npx @codex-agent/cli@latest
|
|
|
17
18
|
|
|
18
19
|
`migrate navigation` discovers navigation-based Markdown context trees, skips incompatible runtime material by default, and writes native indexed context only with `--apply`.
|
|
19
20
|
|
|
20
|
-
`eval` validates focused positive, negative, and overlap skill-routing fixtures plus required and forbidden behavior contracts for every bundled skill and canonical agent.
|
|
21
|
+
`context lint` is read-only and checks catalog lifecycle, provenance hashes, review dates, duplicates, and orphan documents; `--strict` makes warnings fail. `eval` validates focused positive, negative, and overlap skill-routing fixtures plus required and forbidden behavior contracts for every bundled skill and canonical agent. The source workspace additionally provides opt-in model execution through `npm run eval:model`; it is not part of the published CLI or offline gate.
|
package/dist/codex-agent.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.mjs
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs10 from "node:fs";
|
|
5
|
+
import path10 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/core.mjs
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import fs9 from "node:fs";
|
|
9
|
+
import path9 from "node:path";
|
|
10
10
|
|
|
11
11
|
// ../../plugins/codex-agent/scripts/context-project.mjs
|
|
12
12
|
import crypto4 from "node:crypto";
|
|
@@ -21,7 +21,7 @@ var agentProfiles = [
|
|
|
21
21
|
"name": "architecture_analyst",
|
|
22
22
|
"description": "Read-only architecture analyst for impact maps, boundaries, contracts, alternatives, migrations, and rollback-sensitive changes.",
|
|
23
23
|
"sandboxMode": "read-only",
|
|
24
|
-
"developerInstructions": "# Architecture Analyst\n\n## Mission\n\nTurn an approved or proposed repository change into an evidence-backed impact model that clarifies boundaries, contracts, alternatives, migration risk, and implementation consequences.\n\n## Operating contract\n\n- Work read-only. Do not implement the design or create architecture artifacts unless explicitly requested.\n- Base conclusions on repository structure, callers, tests, configuration, and selected project context.\n- Preserve approved product direction while surfacing material architecture decisions still open.\n- Prefer the smallest architecture change that satisfies the outcome.\n\n## Critical rules\n\n1. Identify current ownership and data flow before proposing new components.\n2. Distinguish observed architecture, inferred intent, and proposed change.\n3. Trace impact through public contracts, storage, processes, configuration, deployment, and tests.\n4. Present meaningful alternatives only; do not manufacture options when one local pattern clearly fits.\n5. Evaluate compatibility, migration, rollback, operational ownership, and security boundaries.\n6. Never hide an irreversible or externally visible decision inside implementation detail.\n7. Use diagrams only when they clarify relationships that prose cannot express compactly.\n\n## Analysis decisions\n\n- Reuse an existing module when it already owns the responsibility and extension preserves cohesion.\n- Propose a new boundary when ownership, lifecycle, data, or failure isolation is materially distinct.\n- Define interfaces before suggesting parallel implementation across components.\n- Require a migration plan for persistent data, public APIs, configuration keys, or generated artifacts.\n- Treat new services, production dependencies, credentials, and permission models as explicit decisions.\n\n## Workflow\n\n1. Restate outcome, constraints, exclusions, and open decisions.\n2. Map current components, entrypoints, owners, contracts, and state transitions.\n3. Identify affected callers, data, configuration, tests, operations, and trust boundaries.\n4. Develop the smallest viable design and any credible alternative.\n5. Compare tradeoffs using repository-specific evidence.\n6. Define contracts, sequencing, migration, rollback, and validation implications.\n7. Return the architecture packet for planning.\n\n## Quality rubric\n\n- Evidence: current-state claims cite repository paths.\n- Cohesion: responsibilities and ownership remain clear.\n- Compatibility: callers and persistent state are accounted for.\n- Operability: rollout, failure, observability, and rollback are considered.\n- Economy: proposed structure is no larger than needed.\n\n## Return contract\n\nReturn one status: `READY`, `DECISION_REQUIRED`, or `NEEDS_CONTEXT`, followed by:\n\n1. `Current state` \u2014 evidence-backed component and data-flow map.\n2. `Impact map` \u2014 affected modules, contracts, state, operations, and tests.\n3. `Recommended design` \u2014 responsibilities and interfaces.\n4. `Alternatives and tradeoffs` \u2014 only credible options.\n5. `Migration and rollback`.\n6. `Decisions required` and implementation consequences.\n\n## Avoid\n\n- Framework-first redesigns.\n- Generic diagrams disconnected from code.\n- Treating inferred conventions as declared rules.\n- Hiding product choices in technical terminology."
|
|
24
|
+
"developerInstructions": "# Architecture Analyst\n\n## Mission\n\nTurn an approved or proposed repository change into an evidence-backed impact model that clarifies boundaries, contracts, alternatives, migration risk, and implementation consequences.\n\n## Operating contract\n\n- Work read-only. Do not implement the design or create architecture artifacts unless explicitly requested.\n- Base conclusions on repository structure, callers, tests, configuration, and selected project context.\n- Preserve approved product direction while surfacing material architecture decisions still open.\n- Prefer the smallest architecture change that satisfies the outcome.\n\n## Critical rules\n\n1. Identify current ownership and data flow before proposing new components.\n2. Distinguish observed architecture, inferred intent, and proposed change.\n3. Trace impact through public contracts, storage, processes, configuration, deployment, and tests.\n4. Present meaningful alternatives only; do not manufacture options when one local pattern clearly fits.\n5. Evaluate compatibility, migration, rollback, operational ownership, and security boundaries.\n6. Never hide an irreversible or externally visible decision inside implementation detail.\n7. Use diagrams only when they clarify relationships that prose cannot express compactly.\n\n## Analysis decisions\n\n- Reuse an existing module when it already owns the responsibility and extension preserves cohesion.\n- Propose a new boundary when ownership, lifecycle, data, or failure isolation is materially distinct.\n- Define interfaces before suggesting parallel implementation across components.\n- Require a migration plan for persistent data, public APIs, configuration keys, or generated artifacts.\n- Treat new services, production dependencies, credentials, and permission models as explicit decisions.\n\n## Workflow\n\n1. Restate outcome, constraints, exclusions, and open decisions.\n2. Map current components, entrypoints, owners, contracts, and state transitions.\n3. Identify affected callers, data, configuration, tests, operations, and trust boundaries.\n4. Develop the smallest viable design and any credible alternative.\n5. Compare tradeoffs using repository-specific evidence.\n6. Define contracts, sequencing, migration, rollback, and validation implications.\n7. Return the architecture packet for planning.\n\n## Quality rubric\n\n- Evidence: current-state claims cite repository paths.\n- Cohesion: responsibilities and ownership remain clear.\n- Compatibility: callers and persistent state are accounted for.\n- Operability: rollout, failure, observability, and rollback are considered.\n- Economy: proposed structure is no larger than needed.\n\n## Return contract\n\nReturn one status: `READY`, `DECISION_REQUIRED`, or `NEEDS_CONTEXT`, followed by:\n\n1. `Current state` \u2014 evidence-backed component and data-flow map.\n2. `Impact map` \u2014 affected modules, contracts, state, operations, and tests.\n3. `Recommended design` \u2014 responsibilities and interfaces.\n4. `Alternatives and tradeoffs` \u2014 only credible options.\n5. `Migration and rollback`.\n6. `Decisions required` and implementation consequences.\n7. `Spec inputs` \u2014 candidate `NG-*`, `INV-*`, `SEC-*`, `FAIL-*`, and `AO-*` items for the planning skill to accept or revise; this agent does not own the final specification.\n\n## Avoid\n\n- Framework-first redesigns.\n- Generic diagrams disconnected from code.\n- Treating inferred conventions as declared rules.\n- Hiding product choices in technical terminology."
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"source": "build-verifier.md",
|
|
@@ -29,7 +29,7 @@ var agentProfiles = [
|
|
|
29
29
|
"name": "build_verifier",
|
|
30
30
|
"description": "Workspace-write verification specialist for acceptance criteria, tests, builds, generated artifacts, packaging, and residual risk.",
|
|
31
31
|
"sandboxMode": "workspace-write",
|
|
32
|
-
"developerInstructions": "# Build Verifier\n\n## Mission\n\nIndependently determine whether a completed repository change satisfies its acceptance criteria using fresh, proportional evidence, without changing product code.\n\n## Operating contract\n\n- Workspace-write access exists so checks may create caches, builds, coverage, or generated outputs; do not edit source or tests unless the assignment explicitly changes.\n- Verify the final workspace state rather than trusting summaries from other agents.\n- Use repository-required commands and inspect their complete outcomes.\n- Report failures as failures and distinguish implementation defects from environment blockers.\n\n## Required inputs\n\n- Acceptance and done criteria.\n- Claimed changed files and behavior.\n- Applicable instructions and verification commands.\n- Known environment, platform, or integration constraints.\n\n## Critical rules\n\n1. Inspect `git status` and the final diff before running checks.\n2. Map every acceptance criterion to an observable verification method.\n3. Run the narrowest relevant checks first, then required aggregate validation.\n4. Run applicable format, lint, typecheck, schema, build, packaging, and generated-artifact checks.\n5. Exercise runtime or rendered behavior only when static evidence cannot prove the criterion.\n6. Never modify implementation to repair a failure unless explicitly reassigned.\n7. Do not treat skipped, timed-out, flaky, or unavailable checks as passing.\n8. Verify paths, links, manifests, package contents, and installation instructions when the change affects distribution.\n\n## Verification decisions\n\n- Match effort to risk and changed boundaries.\n- Reuse repository commands rather than inventing substitutes.\n- Use targeted inspection when a full integration is unavailable, and label it substitute evidence.\n- Re-run a failing check only after identifying a credible environmental or nondeterministic cause.\n- Stop when every criterion has fresh evidence and required aggregate checks complete.\n\n## Workflow\n\n1. Restate
|
|
32
|
+
"developerInstructions": "# Build Verifier\n\n## Mission\n\nIndependently determine whether a completed repository change satisfies its acceptance criteria using fresh, proportional evidence, without changing product code.\n\n## Operating contract\n\n- Workspace-write access exists so checks may create caches, builds, coverage, or generated outputs; do not edit source or tests unless the assignment explicitly changes.\n- Verify the final workspace state rather than trusting summaries from other agents.\n- Use repository-required commands and inspect their complete outcomes.\n- Report failures as failures and distinguish implementation defects from environment blockers.\n\n## Required inputs\n\n- Acceptance and done criteria.\n- Claimed changed files and behavior.\n- Applicable instructions and verification commands.\n- Known environment, platform, or integration constraints.\n- The complete specification contract for material planned or coordinated work.\n\n## Critical rules\n\n1. Inspect `git status` and the final diff before running checks.\n2. Map every acceptance criterion to an observable verification method.\n3. Run the narrowest relevant checks first, then required aggregate validation.\n4. Run applicable format, lint, typecheck, schema, build, packaging, and generated-artifact checks.\n5. Exercise runtime or rendered behavior only when static evidence cannot prove the criterion.\n6. Never modify implementation to repair a failure unless explicitly reassigned.\n7. Do not treat skipped, timed-out, flaky, or unavailable checks as passing.\n8. Verify paths, links, manifests, package contents, and installation instructions when the change affects distribution.\n\n## Verification decisions\n\n- Match effort to risk and changed boundaries.\n- Reuse repository commands rather than inventing substitutes.\n- Use targeted inspection when a full integration is unavailable, and label it substitute evidence.\n- Re-run a failing check only after identifying a credible environmental or nondeterministic cause.\n- Stop when every criterion has fresh evidence and required aggregate checks complete.\n\n## Workflow\n\n1. Restate `AO-*` acceptance oracles as a verification matrix, with legacy criteria as a compatibility fallback.\n2. Inspect changed and untracked files for scope and artifacts.\n3. Run narrow tests for changed behavior.\n4. Run repository-mandated aggregate checks.\n5. Validate builds, schemas, packages, generated content, and runtime behavior as applicable.\n6. Review warnings, skipped work, and environment limitations.\n7. Audit `INV-*`, `SEC-*`, `FAIL-*`, compatibility, and absence of work in `NG-*`.\n8. Return the evidence report without changing code.\n\n## Result classification\n\n- `VERIFIED`: every criterion and required check passed.\n- `VERIFIED_WITH_GAPS`: delivered behavior has strong evidence but a material environment-dependent path was not exercised.\n- `IMPLEMENTATION_FAILED`: a check demonstrates a defect or unmet criterion.\n- `ENVIRONMENT_BLOCKED`: the environment prevents required evidence and no safe substitute proves the criterion.\n\n## Return contract\n\nReturn the classification followed by:\n\n1. `Criteria` \u2014 criterion \u2192 evidence \u2192 result.\n2. `Commands` \u2014 exact command and outcome.\n3. `Artifacts inspected`.\n4. `Not validated` \u2014 material gaps only.\n5. `Residual risk`.\n\n## Avoid\n\n- Fixing the code while acting as independent verifier.\n- Reusing stale command output.\n- Claiming a build proves runtime behavior.\n- Omitting warnings that affect confidence."
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"source": "code-reviewer.md",
|
|
@@ -69,7 +69,7 @@ var agentProfiles = [
|
|
|
69
69
|
"name": "implementer",
|
|
70
70
|
"description": "Workspace-write implementation specialist for one bounded approved repository task with incremental validation and evidence.",
|
|
71
71
|
"sandboxMode": "workspace-write",
|
|
72
|
-
"developerInstructions": "# Implementer\n\n## Mission\n\nDeliver one bounded task from an approved change using repository evidence, existing patterns, incremental validation, and strict preservation of unrelated user work.\n\n## Operating contract\n\n- Stay inside the assigned behavioral scope and likely file set unless evidence requires a small adjacent change.\n- Treat pre-existing modifications and untracked files as user-owned.\n- Use supplied context first, then inspect the nearest implementation and tests before editing.\n- Continue through ordinary in-scope corrections without repeated approval; stop only when authority, architecture, or risk materially changes.\n\n## Required inputs\n\n- Task outcome, included and excluded scope, and done criteria.\n- Applicable instructions and selected context paths.\n- Reference source and test files.\n- Dependencies or contracts produced by prerequisite tasks.\n- Exact validation expected for this task.\n\nReturn `NEEDS_CONTEXT` before editing when a missing input would force an architectural guess.\n\n## Critical rules\n\n1. Inspect `git status` and preserve unrelated changes before the first edit.\n2. Reuse nearby naming, data flow, errors, configuration, and test patterns before introducing abstractions.\n3. Make the smallest cohesive change that fully satisfies the assigned behavior.\n4. Validate the narrow behavior immediately after each meaningful increment.\n5. Do not weaken tests, suppress errors, add retries, or change assertions merely to obtain green output.\n6. Do not add production dependencies, perform destructive git operations, publish externally, or change permissions without matching authority.\n7. Never place secrets, private data, or sensitive tool output in code, fixtures, logs, or prompts.\n8. Inspect the final diff and map every changed line to the task outcome or required validation.\n\n## Implementation decisions\n\n- Prefer direct changes to speculative frameworks or generalized helpers.\n- Extend an existing abstraction when it already owns the behavior; create a new one only when responsibilities would otherwise mix.\n- Add or update focused tests when observable behavior changes or regression coverage is absent.\n- Research external APIs only when local source, types, and lockfiles do not establish the contract.\n- Stop on overlapping edits that cannot be preserved safely.\n\n## Workflow\n\n1. Confirm the task packet and restate done criteria.\n2. Inspect worktree state, active instructions, context, references, and nearest tests.\
|
|
72
|
+
"developerInstructions": "# Implementer\n\n## Mission\n\nDeliver one bounded task from an approved change using repository evidence, existing patterns, incremental validation, and strict preservation of unrelated user work.\n\n## Operating contract\n\n- Stay inside the assigned behavioral scope and likely file set unless evidence requires a small adjacent change.\n- Treat pre-existing modifications and untracked files as user-owned.\n- Use supplied context first, then inspect the nearest implementation and tests before editing.\n- Continue through ordinary in-scope corrections without repeated approval; stop only when authority, architecture, or risk materially changes.\n\n## Required inputs\n\n- Task outcome, included and excluded scope, and done criteria.\n- Applicable instructions and selected context paths.\n- Reference source and test files.\n- Dependencies or contracts produced by prerequisite tasks.\n- Exact validation expected for this task.\n- Relevant `specRefs`; preserve referenced non-goals, invariants, and security boundaries and implement referenced failure behavior.\n\nReturn `NEEDS_CONTEXT` before editing when a missing input would force an architectural guess.\n\n## Critical rules\n\n1. Inspect `git status` and preserve unrelated changes before the first edit.\n2. Reuse nearby naming, data flow, errors, configuration, and test patterns before introducing abstractions.\n3. Make the smallest cohesive change that fully satisfies the assigned behavior.\n4. Validate the narrow behavior immediately after each meaningful increment.\n5. Do not weaken tests, suppress errors, add retries, or change assertions merely to obtain green output.\n6. Do not add production dependencies, perform destructive git operations, publish externally, or change permissions without matching authority.\n7. Never place secrets, private data, or sensitive tool output in code, fixtures, logs, or prompts.\n8. Inspect the final diff and map every changed line to the task outcome or required validation.\n\n## Implementation decisions\n\n- Prefer direct changes to speculative frameworks or generalized helpers.\n- Extend an existing abstraction when it already owns the behavior; create a new one only when responsibilities would otherwise mix.\n- Add or update focused tests when observable behavior changes or regression coverage is absent.\n- Research external APIs only when local source, types, and lockfiles do not establish the contract.\n- Stop on overlapping edits that cannot be preserved safely.\n\n## Workflow\n\n1. Confirm the task packet and restate done criteria.\n2. Resolve `specRefs` and return `NEEDS_CONTEXT` when the approved contract is missing or materially drifts.\n3. Inspect worktree state, active instructions, context, references, and nearest tests.\n4. Trace callers, boundaries, and data/state transitions affected by the change.\n5. Implement one cohesive increment with minimal surface area.\n6. Run the narrowest relevant validation; diagnose and fix in-scope failures.\n7. Repeat until every done criterion is satisfied.\n8. Inspect the complete diff for scope, compatibility, debug artifacts, placeholders, and accidental churn.\n9. Run the task's final checks and return fresh evidence.\n\n## Stop and escalation conditions\n\nReturn `BLOCKED` for missing authority, unavailable required credentials, or environment state that prevents progress. Return `NEEDS_CONTEXT` for unresolved architecture or contract gaps. Return `DONE_WITH_CONCERNS` when behavior is delivered but material validation cannot run. Do not silently broaden scope.\n\n## Quality rubric\n\n- Correctness: public behavior and failure paths match the task.\n- Fit: code follows local architecture and naming.\n- Scope: every modification is necessary and unrelated work is preserved.\n- Testability: changed behavior has proportional evidence.\n- Maintainability: data flow and responsibilities remain clear.\n- Safety: trust boundaries, secrets, destructive actions, and compatibility are respected.\n\n## Return contract\n\nReturn one status: `DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, or `BLOCKED`, followed by:\n\n1. `Changed` \u2014 delivered behavior and exact files.\n2. `Criteria` \u2014 done criterion mapped to implementation evidence.\n3. `Validated` \u2014 exact commands and outcomes.\n4. `Not validated` \u2014 material gaps and why.\n5. `Concerns` \u2014 residual risk, assumptions, or follow-up.\n\n## Avoid\n\n- Refactoring unrelated code while nearby.\n- Replacing user changes with generated output.\n- Claiming completion from code inspection alone when executable checks exist.\n- Leaving TODOs, debug output, dead code, or temporary artifacts."
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"source": "task-planner.md",
|
|
@@ -77,7 +77,7 @@ var agentProfiles = [
|
|
|
77
77
|
"name": "task_planner",
|
|
78
78
|
"description": "Read-only planner for atomic, dependency-aware repository tasks with scope, handoff, validation, and completion contracts.",
|
|
79
79
|
"sandboxMode": "read-only",
|
|
80
|
-
"developerInstructions": "# Task Planner\n\n## Mission\n\nConvert approved multi-component scope into an executable task graph whose nodes are bounded, dependency-aware, safe to coordinate, and independently verifiable.\n\n## Operating contract\n\n- Work read-only. Do not edit repository files, create planning artifacts, or expand the approved product scope.\n- Preserve explicit exclusions and decisions already made by the user.\n- Plan by observable outcomes and contracts rather than arbitrary file counts.\n- Prefer a small critical path over a speculative backlog.\n\n## Required inputs\n\n- Approved outcome and scope.\n- Acceptance or exit criteria.\n- Active instructions and selected context paths.\n- Relevant architecture, source, test, and external-contract evidence.\n- Known constraints, risks, and user-owned worktree boundaries.\n\nIf any input is missing and would change architecture or sequencing, return `NEEDS_CONTEXT` before decomposing.\n\n## Critical rules\n\n1. Every task must have an observable outcome, included and excluded scope, context, dependencies, preferred role, validation, and measurable done criteria.\n2. Separate tasks at behavioral or contract boundaries, not merely by file.\n3. Mark work parallel-safe only after checking dependencies, overlapping files, shared generated state, migrations, and external resources.\n4. Define interfaces or data contracts before parallel tasks that consume them.\n5. Put integration and verification after their prerequisites; do not hide them inside a vague final task.\n6. Do not assign two writers to the same file or shared state concurrently.\n7. Surface unresolved assumptions and high-impact decisions instead of embedding guesses in tasks.\n\n## Planning decisions\n\n- Keep a straightforward one-to-three-file change as one implementation task plus validation.\n- Split work when components have independent acceptance criteria, different owners, or explicit contracts.\n- Use a discovery or architecture task only when evidence is genuinely missing; do not plan redundant analysis.\n- Add external research only for version-sensitive behavior not established locally.\n- Place tests with the behavior they prove unless a separate test specialist owns a non-overlapping test-only task.\n\n## Workflow\n\n1. Restate outcome, exclusions, constraints, and exit criteria.\n2. Map components, contracts, state transitions, and integration points.\n3. Identify risks, unknowns, and decisions that gate decomposition.\n4. Create atomic tasks and their complete handoff packets.\n5. Build the dependency graph and identify the critical path.\n6. Analyze file and state overlap before proposing concurrency.\n7. Add integration and final verification tasks.\n8. Audit that every exit criterion maps to at least one task and validation step.\n\n## Task packet\n\nEach task contains:\n\n- `id`\n- `outcome`\n- `scope` with included and excluded behavior\n- `context` with instruction, standard, and reference paths\n- `inputs` and expected `outputs`\n- `dependsOn`\n- `parallelSafe` with overlap rationale\n- `agent`\n- `validation`\n- `doneWhen`\n\n## Quality rubric\n\n- Completeness: every exit criterion is covered.\n- Atomicity: one agent can finish the task in a focused turn.\n- Sequencing: dependencies and contracts precede consumers.\n- Coordination safety: parallel claims include overlap evidence.\n- Verifiability: completion is observable rather than subjective.\n\n## Return contract\n\nReturn one status: `READY` or `NEEDS_CONTEXT`, then provide:\n\n1. Outcome, exclusions, assumptions, and exit criteria.\n2. Dependency-ordered task graph in canonical task-packet shape.\n3. Parallel batches with overlap rationale.\n4. Critical path.\n5. Risks, unresolved questions, and rollback-sensitive tasks.\n\n## Avoid\n\n- One task per file.\n- Time estimates presented as facts.\n- Large catch-all tasks such as \u201Cimplement feature\u201D.\n- Concurrency based only on dependency absence.\n- Creating durable plan files unless explicitly requested."
|
|
80
|
+
"developerInstructions": "# Task Planner\n\n## Mission\n\nConvert approved multi-component scope into an executable task graph whose nodes are bounded, dependency-aware, safe to coordinate, and independently verifiable.\n\n## Operating contract\n\n- Work read-only. Do not edit repository files, create planning artifacts, or expand the approved product scope.\n- Preserve explicit exclusions and decisions already made by the user.\n- Plan by observable outcomes and contracts rather than arbitrary file counts.\n- Prefer a small critical path over a speculative backlog.\n\n## Required inputs\n\n- Approved outcome and scope.\n- Acceptance or exit criteria.\n- The approved specification contract for material coordinated work.\n- Active instructions and selected context paths.\n- Relevant architecture, source, test, and external-contract evidence.\n- Known constraints, risks, and user-owned worktree boundaries.\n\nIf any input is missing and would change architecture or sequencing, return `NEEDS_CONTEXT` before decomposing.\n\n## Critical rules\n\n1. Every task must have an observable outcome, included and excluded scope, context, dependencies, preferred role, validation, and measurable done criteria.\n2. Separate tasks at behavioral or contract boundaries, not merely by file.\n3. Mark work parallel-safe only after checking dependencies, overlapping files, shared generated state, migrations, and external resources.\n4. Define interfaces or data contracts before parallel tasks that consume them.\n5. Put integration and verification after their prerequisites; do not hide them inside a vague final task.\n6. Do not assign two writers to the same file or shared state concurrently.\n7. Surface unresolved assumptions and high-impact decisions instead of embedding guesses in tasks.\n\n## Planning decisions\n\n- Keep a straightforward one-to-three-file change as one implementation task plus validation.\n- Split work when components have independent acceptance criteria, different owners, or explicit contracts.\n- Use a discovery or architecture task only when evidence is genuinely missing; do not plan redundant analysis.\n- Add external research only for version-sensitive behavior not established locally.\n- Place tests with the behavior they prove unless a separate test specialist owns a non-overlapping test-only task.\n\n## Workflow\n\n1. Restate outcome, exclusions, constraints, and exit criteria.\n2. Map components, contracts, state transitions, and integration points.\n3. Identify risks, unknowns, and decisions that gate decomposition.\n4. Create atomic tasks and their complete handoff packets.\n5. Build the dependency graph and identify the critical path.\n6. Analyze file and state overlap before proposing concurrency.\n7. Add integration and final verification tasks.\n8. Audit that every exit criterion maps to at least one task and validation step.\n\n## Task packet\n\nEach task contains:\n\n- `id`\n- `outcome`\n- `scope` with included and excluded behavior\n- `context` with instruction, standard, and reference paths\n- `specRefs` with only the relevant `NG-*`, `INV-*`, `SEC-*`, `FAIL-*`, and `AO-*` identifiers\n- `inputs` and expected `outputs`\n- `dependsOn`\n- `parallelSafe` with overlap rationale\n- `agent`\n- `validation`\n- `doneWhen`\n\n## Quality rubric\n\n- Completeness: every exit criterion is covered.\n- Atomicity: one agent can finish the task in a focused turn.\n- Sequencing: dependencies and contracts precede consumers.\n- Coordination safety: parallel claims include overlap evidence.\n- Verifiability: completion is observable rather than subjective.\n\n## Return contract\n\nReturn one status: `READY` or `NEEDS_CONTEXT`, then provide:\n\n1. Outcome, exclusions, assumptions, and exit criteria.\n2. Dependency-ordered task graph in canonical task-packet shape.\n3. Parallel batches with overlap rationale.\n4. Critical path.\n5. Risks, unresolved questions, and rollback-sensitive tasks.\n\n## Avoid\n\n- One task per file.\n- Time estimates presented as facts.\n- Large catch-all tasks such as \u201Cimplement feature\u201D.\n- Concurrency based only on dependency absence.\n- Creating durable plan files unless explicitly requested."
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"source": "test-engineer.md",
|
|
@@ -225,15 +225,65 @@ var safeRelativePath = (value, label = "Path") => {
|
|
|
225
225
|
|
|
226
226
|
// ../../plugins/codex-agent/scripts/lib/context-index.mjs
|
|
227
227
|
var ROOT_FIELDS = /* @__PURE__ */ new Set(["$schema", "version", "entries"]);
|
|
228
|
-
var
|
|
228
|
+
var BASE_ENTRY_FIELDS = ["id", "path", "summary", "tags", "priority"];
|
|
229
|
+
var V1_ENTRY_FIELDS = new Set(BASE_ENTRY_FIELDS);
|
|
230
|
+
var V2_ENTRY_FIELDS = /* @__PURE__ */ new Set([
|
|
231
|
+
...BASE_ENTRY_FIELDS,
|
|
232
|
+
"kind",
|
|
233
|
+
"scope",
|
|
234
|
+
"confidence",
|
|
235
|
+
"status",
|
|
236
|
+
"recordedAt",
|
|
237
|
+
"lastVerifiedAt",
|
|
238
|
+
"reviewAfter",
|
|
239
|
+
"aliases",
|
|
240
|
+
"related",
|
|
241
|
+
"supersedes",
|
|
242
|
+
"supersededBy",
|
|
243
|
+
"evidence"
|
|
244
|
+
]);
|
|
229
245
|
var PRIORITIES = ["critical", "high", "medium", "low"];
|
|
246
|
+
var KINDS = ["architecture", "standard", "project", "decision", "constraint", "operation", "domain", "pitfall", "imported"];
|
|
247
|
+
var CONFIDENCE = ["high", "medium", "low", "unknown"];
|
|
248
|
+
var STATUSES = ["active", "conflicted", "superseded"];
|
|
249
|
+
var EVIDENCE_TYPES = ["repository", "external", "decision"];
|
|
230
250
|
var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
231
251
|
var PATH_PATTERN = /^(?!\/)(?!.*\.\.\/).+\.md$/;
|
|
232
252
|
var TAG_PATTERN = /^[a-z0-9_-]+$/;
|
|
253
|
+
var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
254
|
+
var SHA_PATTERN = /^[0-9a-f]{64}$/;
|
|
233
255
|
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f\u2028\u2029]/;
|
|
234
256
|
var isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
235
257
|
var unsupportedFields = (value, allowed) => Object.keys(value).filter((field) => !allowed.has(field)).sort();
|
|
236
258
|
var codePointLength = (value) => [...value].length;
|
|
259
|
+
var isDateString = (value) => typeof value === "string" && DATE_PATTERN.test(value) && !Number.isNaN(Date.parse(`${value}T00:00:00Z`)) && (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toISOString().slice(0, 10) === value;
|
|
260
|
+
var contextDate = (date = /* @__PURE__ */ new Date()) => date.toISOString().slice(0, 10);
|
|
261
|
+
var inferContextKind = (entryPath) => {
|
|
262
|
+
const normalized = String(entryPath ?? "").toLowerCase();
|
|
263
|
+
if (normalized.startsWith("architecture/")) return "architecture";
|
|
264
|
+
if (normalized.startsWith("standards/")) return "standard";
|
|
265
|
+
if (normalized.startsWith("project-intelligence/")) return "project";
|
|
266
|
+
if (normalized.startsWith("decisions/")) return "decision";
|
|
267
|
+
if (normalized.startsWith("constraints/")) return "constraint";
|
|
268
|
+
if (normalized.startsWith("operations/")) return "operation";
|
|
269
|
+
if (normalized.startsWith("domain/")) return "domain";
|
|
270
|
+
if (normalized.startsWith("pitfalls/")) return "pitfall";
|
|
271
|
+
return "imported";
|
|
272
|
+
};
|
|
273
|
+
var upgradeContextIndexEntry = (entry, { recordedAt = contextDate() } = {}) => ({
|
|
274
|
+
...entry,
|
|
275
|
+
kind: entry.kind ?? inferContextKind(entry.path),
|
|
276
|
+
scope: entry.scope ?? (String(entry.path ?? "").split("/").slice(0, -1).join("/") || "repository"),
|
|
277
|
+
confidence: entry.confidence ?? "unknown",
|
|
278
|
+
status: entry.status ?? "active",
|
|
279
|
+
recordedAt: entry.recordedAt ?? recordedAt,
|
|
280
|
+
lastVerifiedAt: entry.lastVerifiedAt ?? recordedAt
|
|
281
|
+
});
|
|
282
|
+
var upgradeContextIndex = (index, options = {}) => ({
|
|
283
|
+
...index?.$schema ? { $schema: index.$schema } : {},
|
|
284
|
+
version: 2,
|
|
285
|
+
entries: (index?.entries ?? []).map((entry) => upgradeContextIndexEntry(entry, options))
|
|
286
|
+
});
|
|
237
287
|
var isContextPath = (value) => typeof value === "string" && !value.includes("\\") && !CONTROL_CHARACTERS.test(value) && PATH_PATTERN.test(value) && !path2.posix.isAbsolute(value) && path2.posix.normalize(value) === value;
|
|
238
288
|
var lstat = (target) => {
|
|
239
289
|
try {
|
|
@@ -388,8 +438,8 @@ var validateContextIndex = (value, options = {}) => {
|
|
|
388
438
|
if (Object.hasOwn(value, "$schema") && typeof value.$schema !== "string") {
|
|
389
439
|
errors.push("context index.$schema must be a string");
|
|
390
440
|
}
|
|
391
|
-
if (!Object.hasOwn(value, "version") || value.version
|
|
392
|
-
errors.push("context index.version must be 1");
|
|
441
|
+
if (!Object.hasOwn(value, "version") || ![1, 2].includes(value.version)) {
|
|
442
|
+
errors.push("context index.version must be 1 or 2");
|
|
393
443
|
}
|
|
394
444
|
if (!Object.hasOwn(value, "entries") || !Array.isArray(value.entries)) {
|
|
395
445
|
errors.push("context index.entries must be an array");
|
|
@@ -398,13 +448,16 @@ var validateContextIndex = (value, options = {}) => {
|
|
|
398
448
|
const ids = /* @__PURE__ */ new Set();
|
|
399
449
|
const paths = /* @__PURE__ */ new Set();
|
|
400
450
|
const filesystemPaths = [];
|
|
451
|
+
const relationships = [];
|
|
452
|
+
const entryItems = /* @__PURE__ */ new Map();
|
|
401
453
|
for (const [position, item] of value.entries.entries()) {
|
|
402
454
|
const label = `context index.entries[${position}]`;
|
|
403
455
|
if (!isObject(item)) {
|
|
404
456
|
errors.push(`${label} must be an object`);
|
|
405
457
|
continue;
|
|
406
458
|
}
|
|
407
|
-
|
|
459
|
+
const entryFields = value.version === 2 ? V2_ENTRY_FIELDS : V1_ENTRY_FIELDS;
|
|
460
|
+
for (const field of unsupportedFields(item, entryFields)) {
|
|
408
461
|
errors.push(`${label} has unsupported field: ${field}`);
|
|
409
462
|
}
|
|
410
463
|
const validId = Object.hasOwn(item, "id") && typeof item.id === "string" && ID_PATTERN.test(item.id);
|
|
@@ -414,6 +467,7 @@ var validateContextIndex = (value, options = {}) => {
|
|
|
414
467
|
errors.push(`context index has duplicate id: ${item.id}`);
|
|
415
468
|
} else {
|
|
416
469
|
ids.add(item.id);
|
|
470
|
+
entryItems.set(item.id, item);
|
|
417
471
|
}
|
|
418
472
|
const validPath = Object.hasOwn(item, "path") && isContextPath(item.path);
|
|
419
473
|
if (!validPath) {
|
|
@@ -445,6 +499,134 @@ var validateContextIndex = (value, options = {}) => {
|
|
|
445
499
|
if (!Object.hasOwn(item, "priority") || !PRIORITIES.includes(item.priority)) {
|
|
446
500
|
errors.push(`${label}.priority must be one of: ${PRIORITIES.join(", ")}`);
|
|
447
501
|
}
|
|
502
|
+
if (value.version === 2) {
|
|
503
|
+
if (!Object.hasOwn(item, "kind") || !KINDS.includes(item.kind)) {
|
|
504
|
+
errors.push(`${label}.kind must be one of: ${KINDS.join(", ")}`);
|
|
505
|
+
}
|
|
506
|
+
if (!Object.hasOwn(item, "scope") || typeof item.scope !== "string" || codePointLength(item.scope) < 2 || codePointLength(item.scope) > 120) {
|
|
507
|
+
errors.push(`${label}.scope must be a string between 2 and 120 characters`);
|
|
508
|
+
}
|
|
509
|
+
if (!Object.hasOwn(item, "confidence") || !CONFIDENCE.includes(item.confidence)) {
|
|
510
|
+
errors.push(`${label}.confidence must be one of: ${CONFIDENCE.join(", ")}`);
|
|
511
|
+
}
|
|
512
|
+
if (!Object.hasOwn(item, "status") || !STATUSES.includes(item.status)) {
|
|
513
|
+
errors.push(`${label}.status must be one of: ${STATUSES.join(", ")}`);
|
|
514
|
+
}
|
|
515
|
+
for (const dateField of ["recordedAt", "lastVerifiedAt"]) {
|
|
516
|
+
if (!Object.hasOwn(item, dateField) || !isDateString(item[dateField])) {
|
|
517
|
+
errors.push(`${label}.${dateField} must use YYYY-MM-DD`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (item.reviewAfter !== void 0 && !isDateString(item.reviewAfter)) {
|
|
521
|
+
errors.push(`${label}.reviewAfter must use YYYY-MM-DD`);
|
|
522
|
+
}
|
|
523
|
+
for (const field of ["aliases", "related", "supersedes", "supersededBy"]) {
|
|
524
|
+
if (item[field] === void 0) continue;
|
|
525
|
+
if (!Array.isArray(item[field]) || item[field].length > 20) {
|
|
526
|
+
errors.push(`${label}.${field} must be an array with at most 20 values`);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
const seen = /* @__PURE__ */ new Set();
|
|
530
|
+
for (const [relationPosition, relation] of item[field].entries()) {
|
|
531
|
+
const validRelation = typeof relation === "string" && (field === "aliases" ? relation.trim().length >= 2 : ID_PATTERN.test(relation));
|
|
532
|
+
if (!validRelation) errors.push(`${label}.${field}[${relationPosition}] is invalid`);
|
|
533
|
+
if (seen.has(relation)) errors.push(`${label}.${field} must contain unique values: ${relation}`);
|
|
534
|
+
seen.add(relation);
|
|
535
|
+
if (field !== "aliases" && relation === item.id) errors.push(`${label}.${field} must not reference its own id`);
|
|
536
|
+
if (field !== "aliases" && validRelation) relationships.push({ label, field, target: relation });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (item.evidence !== void 0) {
|
|
540
|
+
if (!Array.isArray(item.evidence) || item.evidence.length > 20) {
|
|
541
|
+
errors.push(`${label}.evidence must be an array with at most 20 values`);
|
|
542
|
+
} else for (const [evidencePosition, evidence] of item.evidence.entries()) {
|
|
543
|
+
const evidenceLabel = `${label}.evidence[${evidencePosition}]`;
|
|
544
|
+
if (!isObject(evidence)) {
|
|
545
|
+
errors.push(`${evidenceLabel} must be an object`);
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
for (const field of unsupportedFields(evidence, /* @__PURE__ */ new Set([
|
|
549
|
+
"type",
|
|
550
|
+
"locator",
|
|
551
|
+
"note",
|
|
552
|
+
"title",
|
|
553
|
+
"version",
|
|
554
|
+
"sha256",
|
|
555
|
+
"retrievedAt",
|
|
556
|
+
"publishedAt",
|
|
557
|
+
"decidedAt",
|
|
558
|
+
"decisionId"
|
|
559
|
+
]))) {
|
|
560
|
+
errors.push(`${evidenceLabel} has unsupported field: ${field}`);
|
|
561
|
+
}
|
|
562
|
+
if (!EVIDENCE_TYPES.includes(evidence.type)) errors.push(`${evidenceLabel}.type is invalid`);
|
|
563
|
+
if (typeof evidence.locator !== "string" || !evidence.locator || evidence.locator.length > 500) {
|
|
564
|
+
errors.push(`${evidenceLabel}.locator must be a non-empty string of at most 500 characters`);
|
|
565
|
+
} else if (evidence.type === "external") {
|
|
566
|
+
try {
|
|
567
|
+
const url = new URL(evidence.locator);
|
|
568
|
+
if (url.protocol !== "https:") errors.push(`${evidenceLabel}.locator must use https`);
|
|
569
|
+
if (url.username || url.password) errors.push(`${evidenceLabel}.locator must not contain credentials`);
|
|
570
|
+
} catch {
|
|
571
|
+
errors.push(`${evidenceLabel}.locator must be an absolute URL`);
|
|
572
|
+
}
|
|
573
|
+
} else if (path2.posix.isAbsolute(evidence.locator) || path2.posix.normalize(evidence.locator) !== evidence.locator || evidence.locator.includes("\\") || evidence.locator.startsWith("../")) {
|
|
574
|
+
errors.push(`${evidenceLabel}.locator must be a normalized repository-relative path`);
|
|
575
|
+
}
|
|
576
|
+
if (typeof evidence.note !== "string" || evidence.note.trim().length < 5 || evidence.note.length > 300) {
|
|
577
|
+
errors.push(`${evidenceLabel}.note must be a string between 5 and 300 characters`);
|
|
578
|
+
}
|
|
579
|
+
if (["repository", "decision"].includes(evidence.type) && !SHA_PATTERN.test(evidence.sha256 ?? "")) {
|
|
580
|
+
errors.push(`${evidenceLabel}.sha256 is required for repository evidence`);
|
|
581
|
+
} else if (evidence.sha256 !== void 0 && !SHA_PATTERN.test(evidence.sha256)) {
|
|
582
|
+
errors.push(`${evidenceLabel}.sha256 is invalid`);
|
|
583
|
+
}
|
|
584
|
+
for (const dateField of ["retrievedAt", "publishedAt", "decidedAt"]) {
|
|
585
|
+
if (evidence[dateField] !== void 0 && !isDateString(evidence[dateField])) {
|
|
586
|
+
errors.push(`${evidenceLabel}.${dateField} must use YYYY-MM-DD`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
for (const textField of ["title", "version", "decisionId"]) {
|
|
590
|
+
if (evidence[textField] !== void 0 && (typeof evidence[textField] !== "string" || !evidence[textField].trim() || evidence[textField].length > 200)) {
|
|
591
|
+
errors.push(`${evidenceLabel}.${textField} must be a non-empty string of at most 200 characters`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (value.version === 2) {
|
|
599
|
+
for (const relationship of relationships) {
|
|
600
|
+
if (!ids.has(relationship.target)) errors.push(`${relationship.label}.${relationship.field} references unknown id: ${relationship.target}`);
|
|
601
|
+
}
|
|
602
|
+
for (const [id, item] of entryItems) {
|
|
603
|
+
for (const targetId of item.supersedes ?? []) {
|
|
604
|
+
const target = entryItems.get(targetId);
|
|
605
|
+
if (target && !(target.supersededBy ?? []).includes(id)) errors.push(`context index supersession is asymmetric: ${id} supersedes ${targetId}`);
|
|
606
|
+
if (target && target.status !== "superseded") errors.push(`context index superseded entry must have status superseded: ${targetId}`);
|
|
607
|
+
}
|
|
608
|
+
for (const targetId of item.supersededBy ?? []) {
|
|
609
|
+
const target = entryItems.get(targetId);
|
|
610
|
+
if (target && !(target.supersedes ?? []).includes(id)) errors.push(`context index supersession is asymmetric: ${id} is superseded by ${targetId}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
614
|
+
const visited = /* @__PURE__ */ new Set();
|
|
615
|
+
const visit = (id) => {
|
|
616
|
+
if (visiting.has(id)) return true;
|
|
617
|
+
if (visited.has(id)) return false;
|
|
618
|
+
visiting.add(id);
|
|
619
|
+
for (const target of entryItems.get(id)?.supersedes ?? []) if (entryItems.has(target) && visit(target)) return true;
|
|
620
|
+
visiting.delete(id);
|
|
621
|
+
visited.add(id);
|
|
622
|
+
return false;
|
|
623
|
+
};
|
|
624
|
+
for (const id of entryItems.keys()) {
|
|
625
|
+
if (visit(id)) {
|
|
626
|
+
errors.push(`context index supersession graph contains a cycle involving: ${id}`);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
448
630
|
}
|
|
449
631
|
if (filesystem) {
|
|
450
632
|
for (const entryPath of filesystemPaths) validateFilesystemEntry(filesystem, entryPath, errors);
|
|
@@ -1385,11 +1567,11 @@ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
1385
1567
|
"vendor"
|
|
1386
1568
|
]);
|
|
1387
1569
|
var MANAGED_CONTEXT = [
|
|
1388
|
-
["architecture", "architecture/system.md", "System architecture, modules, entrypoints, and detected boundaries.", ["architecture", "modules", "entrypoints"], "high"],
|
|
1389
|
-
["code-quality", "standards/code-quality.md", "Detected source layout, naming, and engineering conventions.", ["code", "quality", "conventions"], "critical"],
|
|
1390
|
-
["testing", "standards/testing.md", "Detected test tooling, locations, and repository commands.", ["test", "verification", "commands"], "high"],
|
|
1391
|
-
["security", "standards/security.md", "Detected security-sensitive boundaries and baseline safeguards.", ["security", "auth", "secrets"], "critical"],
|
|
1392
|
-
["project-intelligence", "project-intelligence/project.md", "Detected stack, package tooling, CI, and project intelligence.", ["project", "stack", "ci"], "medium"]
|
|
1570
|
+
["architecture", "architecture/system.md", "System architecture, modules, entrypoints, and detected boundaries.", ["architecture", "modules", "entrypoints"], "high", ["modules", "entrypoints", "conventions.boundaries"]],
|
|
1571
|
+
["code-quality", "standards/code-quality.md", "Detected source layout, naming, and engineering conventions.", ["code", "quality", "conventions"], "critical", ["conventions", "languages"]],
|
|
1572
|
+
["testing", "standards/testing.md", "Detected test tooling, locations, and repository commands.", ["test", "verification", "commands"], "high", ["testing", "commands"]],
|
|
1573
|
+
["security", "standards/security.md", "Detected security-sensitive boundaries and baseline safeguards.", ["security", "auth", "secrets"], "critical", ["security"]],
|
|
1574
|
+
["project-intelligence", "project-intelligence/project.md", "Detected stack, package tooling, CI, and project intelligence.", ["project", "stack", "ci"], "medium", ["project", "packageManager", "languages", "frameworks", "ciCd"]]
|
|
1393
1575
|
];
|
|
1394
1576
|
var CODEX_AGENT_IGNORE_RULES = [
|
|
1395
1577
|
".codex-agent/analysis.json",
|
|
@@ -1823,6 +2005,30 @@ var managedIndexOwnershipConflicts = (existingIndex) => {
|
|
|
1823
2005
|
return [];
|
|
1824
2006
|
});
|
|
1825
2007
|
};
|
|
2008
|
+
var nestedValue = (value, dottedPath) => dottedPath.split(".").reduce((current, key) => current?.[key], value);
|
|
2009
|
+
var collectSignalEvidence = (value) => {
|
|
2010
|
+
if (!value || typeof value !== "object") return [];
|
|
2011
|
+
return [
|
|
2012
|
+
...Array.isArray(value.evidence) ? value.evidence : [],
|
|
2013
|
+
...Object.values(value).flatMap((item) => collectSignalEvidence(item))
|
|
2014
|
+
];
|
|
2015
|
+
};
|
|
2016
|
+
var managedEvidence = (analysis, id, signalPaths) => {
|
|
2017
|
+
const projectRoot = path5.resolve(analysis.root);
|
|
2018
|
+
const locators = unique(signalPaths.flatMap((signalPath) => collectSignalEvidence(nestedValue(analysis, signalPath)))).map((locator) => String(locator).split("#")[0]).filter((locator) => locator && !path5.isAbsolute(locator));
|
|
2019
|
+
return unique(locators).flatMap((locator) => {
|
|
2020
|
+
const target = path5.resolve(projectRoot, locator);
|
|
2021
|
+
const relativePath = relative(projectRoot, target);
|
|
2022
|
+
if (relativePath.startsWith("../") || path5.isAbsolute(relativePath) || relativePath.startsWith(".codex-agent/context/") || relativePath.startsWith(".agents/context/")) return [];
|
|
2023
|
+
try {
|
|
2024
|
+
assertNoSymlink(projectRoot, target, `Managed context evidence ${locator}`);
|
|
2025
|
+
if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) return [];
|
|
2026
|
+
return [{ type: "repository", locator: relativePath, note: `Repository evidence used to generate ${id}.`, sha256: sha256(fs5.readFileSync(target)) }];
|
|
2027
|
+
} catch {
|
|
2028
|
+
return [];
|
|
2029
|
+
}
|
|
2030
|
+
}).slice(0, 20);
|
|
2031
|
+
};
|
|
1826
2032
|
var renderProjectFiles = (analysis, existingIndex = null) => {
|
|
1827
2033
|
if (containsSensitiveContent(JSON.stringify({ analysis, existingIndex }))) {
|
|
1828
2034
|
throw new Error("Project context rendering input appears to contain a secret or credential");
|
|
@@ -1842,13 +2048,28 @@ var renderProjectFiles = (analysis, existingIndex = null) => {
|
|
|
1842
2048
|
[".codex/config.toml", { kind: "toml", id: "agent-settings", body: "[agents]\nmax_concurrent_threads_per_session = 4\nmax_depth = 1\n\n[features]\nhooks = true" }]
|
|
1843
2049
|
]);
|
|
1844
2050
|
for (const profile of agentProfiles) files.set(`.codex/agents/${profile.file}`, { kind: "toml", id: `profile-${profile.name}`, body: renderProfile(profile) });
|
|
1845
|
-
const
|
|
2051
|
+
const upgradedIndex = upgradeContextIndex(existingIndex ?? { version: 1, entries: [] });
|
|
2052
|
+
const priorEntries = upgradedIndex.entries;
|
|
2053
|
+
const priorByPair = new Map(priorEntries.map((entry) => [`${entry.id}\0${entry.path}`, entry]));
|
|
1846
2054
|
const managedPairs = new Set(MANAGED_CONTEXT.map(([id, file]) => `${id}\0${file}`));
|
|
1847
2055
|
const customEntries = priorEntries.filter((entry) => !managedPairs.has(`${entry.id}\0${entry.path}`));
|
|
1848
2056
|
const index = {
|
|
1849
2057
|
...existingIndex?.$schema ? { $schema: existingIndex.$schema } : {},
|
|
1850
|
-
version:
|
|
1851
|
-
entries: [
|
|
2058
|
+
version: 2,
|
|
2059
|
+
entries: [
|
|
2060
|
+
...MANAGED_CONTEXT.map(([id, file, summary2, tags, priority, signalPaths]) => upgradeContextIndexEntry({
|
|
2061
|
+
...priorByPair.get(`${id}\0${file}`),
|
|
2062
|
+
id,
|
|
2063
|
+
path: file,
|
|
2064
|
+
summary: summary2,
|
|
2065
|
+
tags,
|
|
2066
|
+
priority,
|
|
2067
|
+
status: "active",
|
|
2068
|
+
lastVerifiedAt: contextDate(),
|
|
2069
|
+
evidence: managedEvidence(analysis, id, signalPaths)
|
|
2070
|
+
})),
|
|
2071
|
+
...customEntries
|
|
2072
|
+
]
|
|
1852
2073
|
};
|
|
1853
2074
|
files.set(".codex-agent/context/index.json", { kind: "json", content: `${JSON.stringify(index, null, 2)}
|
|
1854
2075
|
` });
|
|
@@ -2555,7 +2776,7 @@ var refreshContext = (options) => runContextLifecycle({ ...options, operation: "
|
|
|
2555
2776
|
import fs6 from "node:fs";
|
|
2556
2777
|
import path6 from "node:path";
|
|
2557
2778
|
import { pathToFileURL } from "node:url";
|
|
2558
|
-
var
|
|
2779
|
+
var KINDS2 = {
|
|
2559
2780
|
decision: "decisions",
|
|
2560
2781
|
constraint: "constraints",
|
|
2561
2782
|
operation: "operations",
|
|
@@ -2563,7 +2784,7 @@ var KINDS = {
|
|
|
2563
2784
|
pitfall: "pitfalls"
|
|
2564
2785
|
};
|
|
2565
2786
|
var PRIORITIES2 = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
|
|
2566
|
-
var
|
|
2787
|
+
var CONFIDENCE2 = /* @__PURE__ */ new Set(["high", "medium"]);
|
|
2567
2788
|
var PROPOSAL_FIELDS = /* @__PURE__ */ new Set([
|
|
2568
2789
|
"version",
|
|
2569
2790
|
"title",
|
|
@@ -2575,7 +2796,10 @@ var PROPOSAL_FIELDS = /* @__PURE__ */ new Set([
|
|
|
2575
2796
|
"tags",
|
|
2576
2797
|
"priority",
|
|
2577
2798
|
"confidence",
|
|
2578
|
-
"reviewWhen"
|
|
2799
|
+
"reviewWhen",
|
|
2800
|
+
"reviewAfter",
|
|
2801
|
+
"aliases",
|
|
2802
|
+
"supersedes"
|
|
2579
2803
|
]);
|
|
2580
2804
|
var unique2 = (items) => [...new Set(items)];
|
|
2581
2805
|
var safeText2 = (value, limit = 300) => String(value).replace(/[\r\n]+/g, " ").replace(/`/g, "'").trim().slice(0, limit);
|
|
@@ -2608,7 +2832,7 @@ var prepareContextIndex = ({ root, pendingDocuments = [] }) => {
|
|
|
2608
2832
|
if (!fs6.existsSync(contextRoot) && pendingDocuments.length === 0) throw new Error(`Context directory not found: ${contextRoot}`);
|
|
2609
2833
|
assertNoSymlink(projectRoot, contextRoot, "Canonical context root");
|
|
2610
2834
|
const indexPath = path6.join(contextRoot, "index.json");
|
|
2611
|
-
const prior = writable.index;
|
|
2835
|
+
const prior = upgradeContextIndex(writable.index);
|
|
2612
2836
|
const priorByPath = new Map(prior.entries.map((entry) => [entry.path, entry]));
|
|
2613
2837
|
const pending = /* @__PURE__ */ new Map();
|
|
2614
2838
|
for (const [index2, document] of pendingDocuments.entries()) {
|
|
@@ -2634,18 +2858,19 @@ var prepareContextIndex = ({ root, pendingDocuments = [] }) => {
|
|
|
2634
2858
|
...relative2.replace(/\.md$/, "").split("/"),
|
|
2635
2859
|
...title.toLowerCase().split(/[^a-z0-9_-]+/).filter((term) => term.length > 2)
|
|
2636
2860
|
].map(slug).filter(Boolean)).slice(0, 10);
|
|
2637
|
-
return {
|
|
2861
|
+
return upgradeContextIndexEntry({
|
|
2862
|
+
...existing,
|
|
2638
2863
|
id: existing?.id || slug(relative2.replace(/\.md$/, "").replaceAll("/", "-")),
|
|
2639
2864
|
path: relative2,
|
|
2640
2865
|
summary: existing?.summary || firstParagraph(content2, `${title} project context.`),
|
|
2641
2866
|
tags: existing?.tags?.length ? existing.tags : tags,
|
|
2642
2867
|
priority: existing?.priority || "medium"
|
|
2643
|
-
};
|
|
2868
|
+
});
|
|
2644
2869
|
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
2645
2870
|
const schemaPath = path6.join(projectRoot, "schemas", "context-index.schema.json");
|
|
2646
2871
|
const index = {
|
|
2647
2872
|
...fs6.existsSync(schemaPath) ? { $schema: "../../schemas/context-index.schema.json" } : prior.$schema ? { $schema: prior.$schema } : {},
|
|
2648
|
-
version:
|
|
2873
|
+
version: 2,
|
|
2649
2874
|
entries
|
|
2650
2875
|
};
|
|
2651
2876
|
assertValidContextIndex(index, { root: projectRoot, contextRoot, pendingPaths: [...pending.keys()] });
|
|
@@ -2683,9 +2908,9 @@ var validateContextProposal = (proposal, { root } = {}) => {
|
|
|
2683
2908
|
checkString(errors, proposal, "summary", 10, 240);
|
|
2684
2909
|
checkString(errors, proposal, "scope", 2, 120);
|
|
2685
2910
|
checkString(errors, proposal, "contentMarkdown", 20, 1e4);
|
|
2686
|
-
if (!Object.hasOwn(
|
|
2911
|
+
if (!Object.hasOwn(KINDS2, proposal.kind)) errors.push(`kind must be one of: ${Object.keys(KINDS2).join(", ")}`);
|
|
2687
2912
|
if (!PRIORITIES2.has(proposal.priority)) errors.push("priority is invalid");
|
|
2688
|
-
if (!
|
|
2913
|
+
if (!CONFIDENCE2.has(proposal.confidence)) errors.push("confidence must be high or medium");
|
|
2689
2914
|
if (!Array.isArray(proposal.tags) || proposal.tags.length < 1 || proposal.tags.length > 10) errors.push("tags must contain between 1 and 10 values");
|
|
2690
2915
|
else {
|
|
2691
2916
|
if (new Set(proposal.tags).size !== proposal.tags.length) errors.push("tags must be unique");
|
|
@@ -2693,23 +2918,41 @@ var validateContextProposal = (proposal, { root } = {}) => {
|
|
|
2693
2918
|
}
|
|
2694
2919
|
if (!Array.isArray(proposal.evidence) || proposal.evidence.length < 1 || proposal.evidence.length > 20) errors.push("evidence must contain between 1 and 20 entries");
|
|
2695
2920
|
else for (const [index, item] of proposal.evidence.entries()) {
|
|
2696
|
-
|
|
2921
|
+
const allowed = ["type", "path", "url", "note", "title", "version", "retrievedAt", "publishedAt", "decisionId", "decidedAt"];
|
|
2922
|
+
if (!item || typeof item !== "object" || Array.isArray(item) || Object.keys(item).some((key) => !allowed.includes(key))) {
|
|
2697
2923
|
errors.push(`evidence[${index}] is invalid`);
|
|
2698
2924
|
continue;
|
|
2699
2925
|
}
|
|
2700
|
-
|
|
2926
|
+
const type = item.type ?? "repository";
|
|
2927
|
+
if (!["repository", "decision", "external"].includes(type)) errors.push(`evidence[${index}].type is invalid`);
|
|
2928
|
+
if (type === "external") {
|
|
2929
|
+
try {
|
|
2930
|
+
const url = new URL(item.url);
|
|
2931
|
+
if (url.protocol !== "https:" || url.username || url.password) errors.push(`evidence[${index}].url must be an https URL without credentials`);
|
|
2932
|
+
} catch {
|
|
2933
|
+
errors.push(`evidence[${index}].url must be an absolute https URL`);
|
|
2934
|
+
}
|
|
2935
|
+
} else if (typeof item.path !== "string" || !item.path || item.path.length > 300 || path6.isAbsolute(item.path)) {
|
|
2936
|
+
errors.push(`evidence[${index}].path must be repository-relative`);
|
|
2937
|
+
}
|
|
2701
2938
|
if (typeof item.note !== "string" || item.note.trim().length < 5 || item.note.length > 300) errors.push(`evidence[${index}].note is invalid`);
|
|
2702
2939
|
}
|
|
2940
|
+
if (Array.isArray(proposal.evidence) && !proposal.evidence.some((item) => ["repository", "decision"].includes(item?.type ?? "repository"))) {
|
|
2941
|
+
errors.push("evidence must include at least one repository or decision source");
|
|
2942
|
+
}
|
|
2703
2943
|
if (proposal.reviewWhen !== void 0 && (!Array.isArray(proposal.reviewWhen) || proposal.reviewWhen.length > 5 || proposal.reviewWhen.some((item) => typeof item !== "string" || item.trim().length < 5 || item.length > 240))) {
|
|
2704
2944
|
errors.push("reviewWhen must contain up to 5 non-empty strings");
|
|
2705
2945
|
}
|
|
2946
|
+
if (proposal.reviewAfter !== void 0 && !/^\d{4}-\d{2}-\d{2}$/.test(proposal.reviewAfter)) errors.push("reviewAfter must use YYYY-MM-DD");
|
|
2947
|
+
if (proposal.aliases !== void 0 && (!Array.isArray(proposal.aliases) || proposal.aliases.length > 20 || proposal.aliases.some((item) => typeof item !== "string" || item.trim().length < 2))) errors.push("aliases must contain up to 20 non-empty values");
|
|
2948
|
+
if (proposal.supersedes !== void 0 && (!Array.isArray(proposal.supersedes) || proposal.supersedes.length > 20 || proposal.supersedes.some((item) => typeof item !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(item)))) errors.push("supersedes must contain valid context ids");
|
|
2706
2949
|
const combined = JSON.stringify(proposal);
|
|
2707
2950
|
if (combined.includes("codex-agent:context:start") || combined.includes("codex-agent:context:end")) errors.push("proposal must not contain managed marker text");
|
|
2708
2951
|
if (containsSensitiveContent(combined)) errors.push("proposal appears to contain a secret or credential");
|
|
2709
2952
|
if (root && Array.isArray(proposal.evidence)) {
|
|
2710
2953
|
const projectRoot = fs6.realpathSync(path6.resolve(root));
|
|
2711
2954
|
for (const [index, item] of proposal.evidence.entries()) {
|
|
2712
|
-
if (!item || typeof item.path !== "string" || path6.isAbsolute(item.path)) continue;
|
|
2955
|
+
if (!item || item.type === "external" || typeof item.path !== "string" || path6.isAbsolute(item.path)) continue;
|
|
2713
2956
|
const target = path6.resolve(projectRoot, item.path);
|
|
2714
2957
|
if (target !== projectRoot && !target.startsWith(`${projectRoot}${path6.sep}`)) {
|
|
2715
2958
|
errors.push(`evidence[${index}].path escapes the repository`);
|
|
@@ -2721,7 +2964,11 @@ var validateContextProposal = (proposal, { root } = {}) => {
|
|
|
2721
2964
|
errors.push(`evidence[${index}].path must not traverse a symbolic link`);
|
|
2722
2965
|
continue;
|
|
2723
2966
|
}
|
|
2724
|
-
|
|
2967
|
+
const relative2 = slash(path6.relative(projectRoot, target));
|
|
2968
|
+
if (relative2 === ".codex-agent/context" || relative2.startsWith(".codex-agent/context/") || relative2 === ".agents/context" || relative2.startsWith(".agents/context/") || relative2.startsWith(".codex-agent/sessions/") || relative2.startsWith(".codex-agent/backups/")) {
|
|
2969
|
+
errors.push(`evidence[${index}].path must reference primary repository evidence, not derived context`);
|
|
2970
|
+
} else if (!fs6.existsSync(target)) errors.push(`evidence[${index}].path does not exist: ${item.path}`);
|
|
2971
|
+
else if (!fs6.statSync(target).isFile()) errors.push(`evidence[${index}].path must be a file`);
|
|
2725
2972
|
}
|
|
2726
2973
|
}
|
|
2727
2974
|
return { ok: errors.length === 0, errors };
|
|
@@ -2733,15 +2980,32 @@ var normalizeContextProposal = (proposal) => ({
|
|
|
2733
2980
|
summary: proposal.summary.trim(),
|
|
2734
2981
|
scope: proposal.scope.trim(),
|
|
2735
2982
|
contentMarkdown: proposal.contentMarkdown.trim(),
|
|
2736
|
-
evidence: proposal.evidence.map((item) =>
|
|
2983
|
+
evidence: proposal.evidence.map((item) => item.type === "external" ? {
|
|
2984
|
+
type: "external",
|
|
2985
|
+
url: item.url,
|
|
2986
|
+
note: item.note.trim(),
|
|
2987
|
+
...item.title ? { title: item.title.trim() } : {},
|
|
2988
|
+
...item.version ? { version: item.version.trim() } : {},
|
|
2989
|
+
...item.retrievedAt ? { retrievedAt: item.retrievedAt } : {},
|
|
2990
|
+
...item.publishedAt ? { publishedAt: item.publishedAt } : {}
|
|
2991
|
+
} : {
|
|
2992
|
+
type: item.type ?? "repository",
|
|
2993
|
+
path: slash(item.path),
|
|
2994
|
+
note: item.note.trim(),
|
|
2995
|
+
...item.decisionId ? { decisionId: item.decisionId.trim() } : {},
|
|
2996
|
+
...item.decidedAt ? { decidedAt: item.decidedAt } : {}
|
|
2997
|
+
}),
|
|
2737
2998
|
tags: proposal.tags,
|
|
2738
2999
|
priority: proposal.priority,
|
|
2739
3000
|
confidence: proposal.confidence,
|
|
2740
|
-
...proposal.reviewWhen?.length ? { reviewWhen: proposal.reviewWhen.map((item) => item.trim()) } : {}
|
|
3001
|
+
...proposal.reviewWhen?.length ? { reviewWhen: proposal.reviewWhen.map((item) => item.trim()) } : {},
|
|
3002
|
+
...proposal.reviewAfter ? { reviewAfter: proposal.reviewAfter } : {},
|
|
3003
|
+
...proposal.aliases?.length ? { aliases: unique2(proposal.aliases.map((item) => item.trim())) } : {},
|
|
3004
|
+
...proposal.supersedes?.length ? { supersedes: unique2(proposal.supersedes) } : {}
|
|
2741
3005
|
});
|
|
2742
3006
|
var renderContextProposal = (proposal, { recordedAt = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) } = {}) => {
|
|
2743
3007
|
const id = `${proposal.kind}-${slug(proposal.title)}`;
|
|
2744
|
-
const evidence = proposal.evidence.map((item) => `- ${mdCode2(item.path)} \u2014 ${safeText2(item.note)}`).join("\n");
|
|
3008
|
+
const evidence = proposal.evidence.map((item) => `- ${mdCode2(item.type === "external" ? item.url : item.path)} \u2014 ${safeText2(item.note)}`).join("\n");
|
|
2745
3009
|
const review = proposal.reviewWhen?.length ? `
|
|
2746
3010
|
|
|
2747
3011
|
## Review when
|
|
@@ -2804,31 +3068,62 @@ var prepareContextProposal = ({ root, proposal, apply, update }) => {
|
|
|
2804
3068
|
const rendered = renderContextProposal(normalized);
|
|
2805
3069
|
const writable = assertWritableContextCatalog({ root: projectRoot });
|
|
2806
3070
|
const contextRoot = writable.contextRoot;
|
|
2807
|
-
const relativePath = `${
|
|
3071
|
+
const relativePath = `${KINDS2[normalized.kind]}/${slug(normalized.title)}.md`;
|
|
2808
3072
|
const destination = path6.join(contextRoot, ...relativePath.split("/"));
|
|
2809
3073
|
assertInside(contextRoot, destination, "context destination");
|
|
2810
3074
|
assertNoSymlink(projectRoot, contextRoot, "Canonical context root");
|
|
2811
3075
|
assertNoSymlink(projectRoot, destination, "Context destination");
|
|
2812
3076
|
const indexPath = writable.indexPath;
|
|
2813
|
-
const index = writable.index;
|
|
3077
|
+
const index = upgradeContextIndex(writable.index);
|
|
2814
3078
|
const currentIndexContent = fs6.existsSync(indexPath) ? fs6.readFileSync(indexPath, "utf8") : null;
|
|
2815
|
-
const duplicate = index.entries.find((entry) => entry.path !== relativePath && (entry.id === rendered.id || normalizeForComparison(entry.summary) === normalizeForComparison(normalized.summary)));
|
|
3079
|
+
const duplicate = index.entries.find((entry) => entry.path !== relativePath && !(normalized.supersedes ?? []).includes(entry.id) && (entry.id === rendered.id || normalizeForComparison(entry.summary) === normalizeForComparison(normalized.summary)));
|
|
2816
3080
|
if (duplicate) throw new Error(`Duplicate context candidate: ${duplicate.path}`);
|
|
2817
3081
|
const current = fs6.existsSync(destination) ? fs6.readFileSync(destination, "utf8") : null;
|
|
2818
3082
|
const merge = mergeManaged(current, rendered, update);
|
|
2819
3083
|
const priorEntry = index.entries.find((entry) => entry.path === relativePath || entry.id === rendered.id);
|
|
2820
3084
|
if (priorEntry && priorEntry.path !== relativePath) throw new Error(`Context id already belongs to another path: ${priorEntry.path}`);
|
|
2821
|
-
const
|
|
3085
|
+
const evidence = normalized.evidence.map((item) => item.type === "external" ? {
|
|
3086
|
+
type: "external",
|
|
3087
|
+
locator: item.url,
|
|
3088
|
+
note: item.note,
|
|
3089
|
+
retrievedAt: item.retrievedAt ?? contextDate(),
|
|
3090
|
+
...item.title ? { title: item.title } : {},
|
|
3091
|
+
...item.version ? { version: item.version } : {},
|
|
3092
|
+
...item.publishedAt ? { publishedAt: item.publishedAt } : {}
|
|
3093
|
+
} : {
|
|
3094
|
+
type: item.type,
|
|
3095
|
+
locator: item.path,
|
|
3096
|
+
note: item.note,
|
|
3097
|
+
sha256: sha256(fs6.readFileSync(path6.resolve(projectRoot, item.path))),
|
|
3098
|
+
...item.decisionId ? { decisionId: item.decisionId } : {},
|
|
3099
|
+
...item.decidedAt ? { decidedAt: item.decidedAt } : {}
|
|
3100
|
+
});
|
|
3101
|
+
const nextEntry = upgradeContextIndexEntry({
|
|
3102
|
+
...priorEntry,
|
|
2822
3103
|
id: rendered.id,
|
|
2823
3104
|
path: relativePath,
|
|
2824
3105
|
summary: normalized.summary,
|
|
2825
3106
|
tags: unique2([normalized.kind, ...normalized.tags]).slice(0, 10),
|
|
2826
|
-
priority: normalized.priority
|
|
2827
|
-
|
|
3107
|
+
priority: normalized.priority,
|
|
3108
|
+
kind: normalized.kind,
|
|
3109
|
+
scope: normalized.scope,
|
|
3110
|
+
confidence: normalized.confidence,
|
|
3111
|
+
status: "active",
|
|
3112
|
+
lastVerifiedAt: contextDate(),
|
|
3113
|
+
evidence,
|
|
3114
|
+
...normalized.reviewAfter ? { reviewAfter: normalized.reviewAfter } : {},
|
|
3115
|
+
...normalized.aliases?.length ? { aliases: normalized.aliases } : {},
|
|
3116
|
+
...normalized.supersedes?.length ? { supersedes: normalized.supersedes } : {}
|
|
3117
|
+
});
|
|
3118
|
+
for (const supersededId of normalized.supersedes ?? []) {
|
|
3119
|
+
if (supersededId === rendered.id) throw new Error("Context proposal must not supersede itself");
|
|
3120
|
+
if (!index.entries.some((entry) => entry.id === supersededId)) throw new Error(`Context proposal supersedes unknown id: ${supersededId}`);
|
|
3121
|
+
}
|
|
3122
|
+
const supersededEntries = index.entries.map((entry) => (normalized.supersedes ?? []).includes(entry.id) ? upgradeContextIndexEntry({ ...entry, status: "superseded", supersededBy: unique2([...entry.supersededBy ?? [], rendered.id]) }) : entry);
|
|
2828
3123
|
const nextIndex = {
|
|
2829
3124
|
...index.$schema ? { $schema: index.$schema } : {},
|
|
2830
|
-
version:
|
|
2831
|
-
entries: [...
|
|
3125
|
+
version: 2,
|
|
3126
|
+
entries: [...supersededEntries.filter((entry) => entry.path !== relativePath && entry.id !== rendered.id), nextEntry].sort((left, right) => left.path.localeCompare(right.path))
|
|
2832
3127
|
};
|
|
2833
3128
|
assertValidContextIndex(nextIndex, { root: projectRoot, contextRoot, pendingPaths: [relativePath] });
|
|
2834
3129
|
const indexContent = `${JSON.stringify(nextIndex, null, 2)}
|
|
@@ -3141,7 +3436,7 @@ ${MANAGED_END(id)}`;
|
|
|
3141
3436
|
});
|
|
3142
3437
|
}
|
|
3143
3438
|
const indexPath = path7.join(contextRoot, "index.json");
|
|
3144
|
-
const targetIndex = writableCatalog.index;
|
|
3439
|
+
const targetIndex = upgradeContextIndex(writableCatalog.index);
|
|
3145
3440
|
const priorIndexContent = fs7.existsSync(indexPath) ? fs7.readFileSync(indexPath, "utf8") : null;
|
|
3146
3441
|
const changes = [];
|
|
3147
3442
|
const conflicts = [];
|
|
@@ -3195,19 +3490,21 @@ ${MANAGED_END(id)}`;
|
|
|
3195
3490
|
before: current,
|
|
3196
3491
|
content: merge.content
|
|
3197
3492
|
});
|
|
3198
|
-
migrationEntries.push({
|
|
3493
|
+
migrationEntries.push(upgradeContextIndexEntry({
|
|
3199
3494
|
id: candidate.id,
|
|
3200
3495
|
path: candidate.destinationRelative,
|
|
3201
3496
|
summary: candidate.summary,
|
|
3202
3497
|
tags: candidate.tags,
|
|
3203
|
-
priority: candidate.priority
|
|
3204
|
-
|
|
3498
|
+
priority: candidate.priority,
|
|
3499
|
+
kind: "imported",
|
|
3500
|
+
confidence: "low"
|
|
3501
|
+
}));
|
|
3205
3502
|
}
|
|
3206
3503
|
const migratingIds = new Set(migrationEntries.map((entry) => entry.id));
|
|
3207
3504
|
const migratingPaths = new Set(migrationEntries.map((entry) => entry.path));
|
|
3208
3505
|
const nextIndex = {
|
|
3209
3506
|
...targetIndex.$schema ? { $schema: targetIndex.$schema } : {},
|
|
3210
|
-
version:
|
|
3507
|
+
version: 2,
|
|
3211
3508
|
entries: [
|
|
3212
3509
|
...targetIndex.entries.filter((entry) => !migratingIds.has(entry.id) && !migratingPaths.has(entry.path)),
|
|
3213
3510
|
...migrationEntries
|
|
@@ -3297,13 +3594,155 @@ if (process.argv[1] && path7.basename(process.argv[1]) === "navigation-migrate.m
|
|
|
3297
3594
|
}
|
|
3298
3595
|
}
|
|
3299
3596
|
|
|
3597
|
+
// ../../plugins/codex-agent/skills/context-lint/scripts/context-lint.mjs
|
|
3598
|
+
import fs8 from "node:fs";
|
|
3599
|
+
import path8 from "node:path";
|
|
3600
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
3601
|
+
var healthOrder = ["conflict", "orphan", "duplicate", "insufficient-evidence", "review-due", "healthy"];
|
|
3602
|
+
var normalize = (value) => String(value ?? "").normalize("NFKC").toLocaleLowerCase("en-US").replace(/\s+/g, " ").trim();
|
|
3603
|
+
var findingSort = (left, right) => (left.path ?? "").localeCompare(right.path ?? "") || (left.id ?? "").localeCompare(right.id ?? "") || left.code.localeCompare(right.code);
|
|
3604
|
+
var healthFor = (reasons) => healthOrder.find((health) => reasons.includes(health)) ?? "healthy";
|
|
3605
|
+
var lintContext = ({ root, strict = false, now = /* @__PURE__ */ new Date() }) => {
|
|
3606
|
+
const checkedAt = now.toISOString();
|
|
3607
|
+
const resolution = resolveContextCatalog({ root });
|
|
3608
|
+
const findings = [];
|
|
3609
|
+
if (["invalid", "both-divergent"].includes(resolution.state)) {
|
|
3610
|
+
for (const message of resolution.errors.length ? resolution.errors : resolution.warnings) {
|
|
3611
|
+
findings.push({ severity: "error", code: "catalog-invalid", message });
|
|
3612
|
+
}
|
|
3613
|
+
return {
|
|
3614
|
+
ok: false,
|
|
3615
|
+
healthy: false,
|
|
3616
|
+
checkedAt,
|
|
3617
|
+
state: resolution.state,
|
|
3618
|
+
summary: { entries: 0, healthy: 0, findings: findings.length, errors: findings.length, warnings: 0 },
|
|
3619
|
+
entries: [],
|
|
3620
|
+
findings: findings.sort(findingSort)
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
const catalog = getReadableContextCatalog({ root });
|
|
3624
|
+
if (!catalog.index) {
|
|
3625
|
+
findings.push({ severity: "warning", code: "catalog-missing", message: "context index not found" });
|
|
3626
|
+
return {
|
|
3627
|
+
ok: !strict,
|
|
3628
|
+
healthy: false,
|
|
3629
|
+
checkedAt,
|
|
3630
|
+
state: catalog.state,
|
|
3631
|
+
summary: { entries: 0, healthy: 0, findings: 1, errors: 0, warnings: 1 },
|
|
3632
|
+
entries: [],
|
|
3633
|
+
findings
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
const reasonsById = new Map(catalog.index.entries.map((entry) => [entry.id, /* @__PURE__ */ new Set()]));
|
|
3637
|
+
const add = (severity, code, entry, message, health = null) => {
|
|
3638
|
+
findings.push({ severity, code, ...entry?.id ? { id: entry.id } : {}, ...entry?.path ? { path: entry.path } : {}, message });
|
|
3639
|
+
if (health && entry?.id) reasonsById.get(entry.id)?.add(health);
|
|
3640
|
+
};
|
|
3641
|
+
const today = checkedAt.slice(0, 10);
|
|
3642
|
+
for (const entry of catalog.index.entries) {
|
|
3643
|
+
if (catalog.index.version === 1 || !entry.evidence?.length) {
|
|
3644
|
+
add("warning", "evidence-missing", entry, "entry has no machine-verifiable provenance", "insufficient-evidence");
|
|
3645
|
+
}
|
|
3646
|
+
if (entry.status === "conflicted") add("error", "lifecycle-conflicted", entry, "entry is marked conflicted", "conflict");
|
|
3647
|
+
if (entry.reviewAfter && entry.reviewAfter <= today) add("warning", "review-due", entry, `entry review was due on ${entry.reviewAfter}`, "review-due");
|
|
3648
|
+
const localEvidence = (entry.evidence ?? []).filter((evidence) => ["repository", "decision"].includes(evidence.type));
|
|
3649
|
+
if (entry.evidence?.length && !localEvidence.length) {
|
|
3650
|
+
add("warning", "evidence-derived-only", entry, "external evidence alone cannot establish repository-specific context", "insufficient-evidence");
|
|
3651
|
+
}
|
|
3652
|
+
for (const evidence of localEvidence) {
|
|
3653
|
+
const target = path8.resolve(catalog.root, evidence.locator);
|
|
3654
|
+
const relative2 = slash(path8.relative(catalog.root, target));
|
|
3655
|
+
if (relative2.startsWith("../") || path8.isAbsolute(relative2)) {
|
|
3656
|
+
add("error", "evidence-escape", entry, `evidence escapes repository: ${evidence.locator}`, "insufficient-evidence");
|
|
3657
|
+
continue;
|
|
3658
|
+
}
|
|
3659
|
+
if (relative2 === ".codex-agent/context" || relative2.startsWith(".codex-agent/context/") || relative2 === ".agents/context" || relative2.startsWith(".agents/context/")) {
|
|
3660
|
+
add("warning", "evidence-derived", entry, `evidence points to derived context: ${evidence.locator}`, "insufficient-evidence");
|
|
3661
|
+
continue;
|
|
3662
|
+
}
|
|
3663
|
+
let stat;
|
|
3664
|
+
try {
|
|
3665
|
+
assertNoSymlink(catalog.root, target, `context evidence ${evidence.locator}`);
|
|
3666
|
+
} catch {
|
|
3667
|
+
add("error", "evidence-invalid-file", entry, `evidence traverses a symbolic link: ${evidence.locator}`, "insufficient-evidence");
|
|
3668
|
+
continue;
|
|
3669
|
+
}
|
|
3670
|
+
try {
|
|
3671
|
+
stat = fs8.lstatSync(target);
|
|
3672
|
+
} catch {
|
|
3673
|
+
stat = null;
|
|
3674
|
+
}
|
|
3675
|
+
if (!stat) add("error", "evidence-missing-file", entry, `evidence file is missing: ${evidence.locator}`, "insufficient-evidence");
|
|
3676
|
+
else if (stat.isSymbolicLink() || !stat.isFile()) add("error", "evidence-invalid-file", entry, `evidence is not a regular file: ${evidence.locator}`, "insufficient-evidence");
|
|
3677
|
+
else if (sha256(fs8.readFileSync(target)) !== evidence.sha256) add("error", "evidence-digest-mismatch", entry, `evidence changed: ${evidence.locator}`, "conflict");
|
|
3678
|
+
}
|
|
3679
|
+
for (const relatedId of entry.related ?? []) {
|
|
3680
|
+
const related = catalog.index.entries.find((item) => item.id === relatedId);
|
|
3681
|
+
if (related && !(related.related ?? []).includes(entry.id)) add("warning", "relation-asymmetric", entry, `related link is not reciprocal: ${relatedId}`, "conflict");
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
const summaryOwners = /* @__PURE__ */ new Map();
|
|
3685
|
+
const contentOwners = /* @__PURE__ */ new Map();
|
|
3686
|
+
for (const entry of catalog.index.entries.filter((item) => item.status !== "superseded")) {
|
|
3687
|
+
const summaryKey = normalize(entry.summary);
|
|
3688
|
+
const contentKey = sha256(fs8.readFileSync(path8.join(catalog.contextRoot, ...entry.path.split("/"))));
|
|
3689
|
+
for (const [key, owners, label] of [[summaryKey, summaryOwners, "summary"], [contentKey, contentOwners, "content"]]) {
|
|
3690
|
+
const owner = owners.get(key);
|
|
3691
|
+
if (owner && !(entry.supersedes ?? []).includes(owner.id) && !(owner.supersedes ?? []).includes(entry.id)) {
|
|
3692
|
+
add("warning", `duplicate-${label}`, entry, `${label} duplicates ${owner.id}`, "duplicate");
|
|
3693
|
+
reasonsById.get(owner.id)?.add("duplicate");
|
|
3694
|
+
} else if (!owner) owners.set(key, entry);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
const indexed = new Set(catalog.index.entries.map((entry) => entry.path));
|
|
3698
|
+
for (const file of listTreeFiles(catalog.contextRoot).filter((item) => item.relative.toLowerCase().endsWith(".md"))) {
|
|
3699
|
+
if (!indexed.has(file.relative)) add("warning", "orphan-document", { path: file.relative }, "Markdown document is not indexed");
|
|
3700
|
+
}
|
|
3701
|
+
const entries = catalog.index.entries.map((entry) => {
|
|
3702
|
+
const reasons = [...reasonsById.get(entry.id)].sort((left, right) => healthOrder.indexOf(left) - healthOrder.indexOf(right));
|
|
3703
|
+
return { id: entry.id, path: entry.path, lifecycle: entry.status ?? "active", health: healthFor(reasons), reasons };
|
|
3704
|
+
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
3705
|
+
const sortedFindings = findings.sort(findingSort);
|
|
3706
|
+
const errors = sortedFindings.filter((finding) => finding.severity === "error").length;
|
|
3707
|
+
const warnings = sortedFindings.filter((finding) => finding.severity === "warning").length;
|
|
3708
|
+
const healthy = entries.length > 0 && entries.every((entry) => entry.health === "healthy") && warnings === 0 && errors === 0;
|
|
3709
|
+
return {
|
|
3710
|
+
ok: errors === 0 && (!strict || warnings === 0),
|
|
3711
|
+
healthy,
|
|
3712
|
+
checkedAt,
|
|
3713
|
+
state: catalog.state,
|
|
3714
|
+
summary: { entries: entries.length, healthy: entries.filter((entry) => entry.health === "healthy").length, findings: sortedFindings.length, errors, warnings },
|
|
3715
|
+
entries,
|
|
3716
|
+
findings: sortedFindings
|
|
3717
|
+
};
|
|
3718
|
+
};
|
|
3719
|
+
var option3 = (args, name, fallback) => {
|
|
3720
|
+
const index = args.indexOf(name);
|
|
3721
|
+
return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
|
|
3722
|
+
};
|
|
3723
|
+
var main3 = (args = process.argv.slice(2)) => {
|
|
3724
|
+
const result = lintContext({ root: path8.resolve(option3(args, "--root", process.cwd())), strict: args.includes("--strict") });
|
|
3725
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
3726
|
+
`);
|
|
3727
|
+
if (!result.ok) process.exitCode = 1;
|
|
3728
|
+
};
|
|
3729
|
+
if (process.argv[1] && path8.basename(process.argv[1]) === "context-lint.mjs" && import.meta.url === pathToFileURL3(process.argv[1]).href) {
|
|
3730
|
+
try {
|
|
3731
|
+
main3();
|
|
3732
|
+
} catch (error) {
|
|
3733
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3734
|
+
`);
|
|
3735
|
+
process.exitCode = 1;
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3300
3739
|
// src/core.mjs
|
|
3301
3740
|
var listFiles = (root) => {
|
|
3302
|
-
if (!
|
|
3741
|
+
if (!fs9.existsSync(root)) return [];
|
|
3303
3742
|
const files = [];
|
|
3304
3743
|
const visit = (directory) => {
|
|
3305
|
-
for (const entry of
|
|
3306
|
-
const absolute =
|
|
3744
|
+
for (const entry of fs9.readdirSync(directory, { withFileTypes: true })) {
|
|
3745
|
+
const absolute = path9.join(directory, entry.name);
|
|
3307
3746
|
if (entry.isDirectory()) visit(absolute);
|
|
3308
3747
|
else if (entry.isFile()) files.push(absolute);
|
|
3309
3748
|
}
|
|
@@ -3313,41 +3752,41 @@ var listFiles = (root) => {
|
|
|
3313
3752
|
};
|
|
3314
3753
|
var prepareContextMigration = ({ root, source, dryRun = false, force = false }) => {
|
|
3315
3754
|
if (!source) throw new Error("migrate requires --from PATH");
|
|
3316
|
-
const projectRoot =
|
|
3317
|
-
const sourceRoot =
|
|
3318
|
-
if (!
|
|
3319
|
-
const sourceFiles = (
|
|
3755
|
+
const projectRoot = path9.resolve(root);
|
|
3756
|
+
const sourceRoot = path9.resolve(source);
|
|
3757
|
+
if (!fs9.existsSync(sourceRoot)) throw new Error(`Migration source not found: ${sourceRoot}`);
|
|
3758
|
+
const sourceFiles = (fs9.statSync(sourceRoot).isDirectory() ? listFiles(sourceRoot) : [sourceRoot]).filter((file) => file.endsWith(".md"));
|
|
3320
3759
|
if (!sourceFiles.length) throw new Error("Migration source contains no Markdown context files.");
|
|
3321
3760
|
const writableCatalog = assertWritableContextCatalog({ root: projectRoot });
|
|
3322
3761
|
const catalogProjectRoot = writableCatalog.root;
|
|
3323
3762
|
const contextRoot = writableCatalog.contextRoot;
|
|
3324
3763
|
if (!contextRoot) throw new Error("Writable context catalog did not resolve a destination root.");
|
|
3325
|
-
const destinationRoot =
|
|
3764
|
+
const destinationRoot = path9.join(contextRoot, "imported");
|
|
3326
3765
|
const result = { imported: [], unchanged: [], conflicts: [], backedUp: [], dryRun, applied: false };
|
|
3327
3766
|
const documents = [];
|
|
3328
3767
|
const backupPaths = [];
|
|
3329
3768
|
for (const sourceFile of sourceFiles) {
|
|
3330
|
-
const relative2 =
|
|
3331
|
-
const destination =
|
|
3332
|
-
const content =
|
|
3769
|
+
const relative2 = fs9.statSync(sourceRoot).isDirectory() ? path9.relative(sourceRoot, sourceFile) : path9.basename(sourceFile);
|
|
3770
|
+
const destination = path9.join(destinationRoot, relative2);
|
|
3771
|
+
const content = fs9.readFileSync(sourceFile);
|
|
3333
3772
|
if (containsSensitiveContent(content.toString("utf8"))) {
|
|
3334
3773
|
throw new Error(`Migration source appears to contain a secret or credential: ${sourceFile}`);
|
|
3335
3774
|
}
|
|
3336
|
-
if (!
|
|
3337
|
-
result.imported.push(
|
|
3338
|
-
documents.push({ path:
|
|
3775
|
+
if (!fs9.existsSync(destination)) {
|
|
3776
|
+
result.imported.push(path9.relative(catalogProjectRoot, destination));
|
|
3777
|
+
documents.push({ path: path9.relative(contextRoot, destination).split(path9.sep).join("/"), content: content.toString("utf8") });
|
|
3339
3778
|
continue;
|
|
3340
3779
|
}
|
|
3341
|
-
if (content.equals(
|
|
3342
|
-
result.unchanged.push(
|
|
3780
|
+
if (content.equals(fs9.readFileSync(destination))) {
|
|
3781
|
+
result.unchanged.push(path9.relative(catalogProjectRoot, destination));
|
|
3343
3782
|
continue;
|
|
3344
3783
|
}
|
|
3345
3784
|
if (!force) {
|
|
3346
|
-
result.conflicts.push(
|
|
3785
|
+
result.conflicts.push(path9.relative(catalogProjectRoot, destination));
|
|
3347
3786
|
continue;
|
|
3348
3787
|
}
|
|
3349
|
-
result.imported.push(
|
|
3350
|
-
const documentPath =
|
|
3788
|
+
result.imported.push(path9.relative(catalogProjectRoot, destination));
|
|
3789
|
+
const documentPath = path9.relative(contextRoot, destination).split(path9.sep).join("/");
|
|
3351
3790
|
documents.push({ path: documentPath, content: content.toString("utf8") });
|
|
3352
3791
|
backupPaths.push(documentPath);
|
|
3353
3792
|
}
|
|
@@ -3359,7 +3798,7 @@ var prepareContextMigration = ({ root, source, dryRun = false, force = false })
|
|
|
3359
3798
|
index,
|
|
3360
3799
|
backupPaths: [
|
|
3361
3800
|
...backupPaths,
|
|
3362
|
-
...index &&
|
|
3801
|
+
...index && fs9.existsSync(index.path) ? ["index.json"] : []
|
|
3363
3802
|
]
|
|
3364
3803
|
};
|
|
3365
3804
|
};
|
|
@@ -3384,23 +3823,23 @@ var migrateContext = (options) => {
|
|
|
3384
3823
|
var check = (checks, name, ok, detail) => checks.push({ name, ok: Boolean(ok), detail });
|
|
3385
3824
|
var parseJson = (file) => {
|
|
3386
3825
|
try {
|
|
3387
|
-
return { value: JSON.parse(
|
|
3826
|
+
return { value: JSON.parse(fs9.readFileSync(file, "utf8")) };
|
|
3388
3827
|
} catch (error) {
|
|
3389
3828
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
3390
3829
|
}
|
|
3391
3830
|
};
|
|
3392
3831
|
var diagnoseProject = ({ root }) => {
|
|
3393
|
-
const projectRoot =
|
|
3832
|
+
const projectRoot = path9.resolve(root);
|
|
3394
3833
|
const checks = [];
|
|
3395
3834
|
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0], 10);
|
|
3396
3835
|
check(checks, "node", nodeMajor >= 20, `Node.js ${process.versions.node}; requires 20 or newer`);
|
|
3397
|
-
const manifest =
|
|
3398
|
-
const isSourceWorkspace =
|
|
3836
|
+
const manifest = path9.join(projectRoot, "plugins", "codex-agent", ".codex-plugin", "plugin.json");
|
|
3837
|
+
const isSourceWorkspace = fs9.existsSync(manifest);
|
|
3399
3838
|
check(checks, "mode", true, isSourceWorkspace ? "plugin source workspace" : "initialized consumer project");
|
|
3400
3839
|
if (isSourceWorkspace) {
|
|
3401
|
-
const marketplace =
|
|
3402
|
-
check(checks, "marketplace",
|
|
3403
|
-
if (
|
|
3840
|
+
const marketplace = path9.join(projectRoot, ".agents", "plugins", "marketplace.json");
|
|
3841
|
+
check(checks, "marketplace", fs9.existsSync(marketplace), marketplace);
|
|
3842
|
+
if (fs9.existsSync(marketplace)) {
|
|
3404
3843
|
const parsed2 = parseJson(marketplace);
|
|
3405
3844
|
check(checks, "marketplace-json", !parsed2.error, parsed2.error || parsed2.value.name);
|
|
3406
3845
|
check(
|
|
@@ -3415,10 +3854,10 @@ var diagnoseProject = ({ root }) => {
|
|
|
3415
3854
|
check(checks, "plugin-json", !parsed.error, parsed.error || parsed.value.name);
|
|
3416
3855
|
check(checks, "plugin-name", parsed.value?.name === "codex-agent", parsed.value?.name || "missing");
|
|
3417
3856
|
} else {
|
|
3418
|
-
const config =
|
|
3419
|
-
const agents =
|
|
3857
|
+
const config = path9.join(projectRoot, ".codex", "config.toml");
|
|
3858
|
+
const agents = path9.join(projectRoot, ".codex", "agents");
|
|
3420
3859
|
const profiles = listFiles(agents).filter((file) => file.endsWith(".toml"));
|
|
3421
|
-
check(checks, "project-config",
|
|
3860
|
+
check(checks, "project-config", fs9.existsSync(config), config);
|
|
3422
3861
|
check(checks, "project-agents", profiles.length === agentProfiles.length, `${profiles.length}/${agentProfiles.length} profiles in ${agents}`);
|
|
3423
3862
|
}
|
|
3424
3863
|
const resolvedCatalog = resolveContextCatalog({ root: projectRoot });
|
|
@@ -3429,38 +3868,38 @@ var diagnoseProject = ({ root }) => {
|
|
|
3429
3868
|
} catch (error) {
|
|
3430
3869
|
check(checks, "context-readable", false, error instanceof Error ? error.message : String(error));
|
|
3431
3870
|
}
|
|
3432
|
-
const contextIndex = readableCatalog2?.indexPath ?? (readableCatalog2?.root ?
|
|
3433
|
-
check(checks, "context-index", Boolean(contextIndex &&
|
|
3434
|
-
if (contextIndex &&
|
|
3871
|
+
const contextIndex = readableCatalog2?.indexPath ?? (readableCatalog2?.root ? path9.join(readableCatalog2.root, "index.json") : null);
|
|
3872
|
+
check(checks, "context-index", Boolean(contextIndex && fs9.existsSync(contextIndex)), contextIndex ?? "context index not found");
|
|
3873
|
+
if (contextIndex && fs9.existsSync(contextIndex)) {
|
|
3435
3874
|
const parsed = parseJson(contextIndex);
|
|
3436
3875
|
check(checks, "context-json", !parsed.error, parsed.error || `${parsed.value.entries?.length ?? 0} entries`);
|
|
3437
|
-
const contextRoot =
|
|
3876
|
+
const contextRoot = path9.dirname(contextIndex);
|
|
3438
3877
|
const invalid = (parsed.value?.entries ?? []).filter((entry) => {
|
|
3439
|
-
const target =
|
|
3440
|
-
return !target.startsWith(`${contextRoot}${
|
|
3878
|
+
const target = path9.resolve(contextRoot, entry.path || "");
|
|
3879
|
+
return !target.startsWith(`${contextRoot}${path9.sep}`) || !fs9.existsSync(target);
|
|
3441
3880
|
});
|
|
3442
3881
|
check(checks, "context-paths", invalid.length === 0, invalid.map((entry) => entry.path).join(", ") || "all paths valid");
|
|
3443
3882
|
}
|
|
3444
3883
|
if (isSourceWorkspace) {
|
|
3445
|
-
const skillsRoot =
|
|
3446
|
-
const skillFiles = listFiles(skillsRoot).filter((file) => file.endsWith(`${
|
|
3447
|
-
const skillDirectories =
|
|
3884
|
+
const skillsRoot = path9.join(projectRoot, "plugins", "codex-agent", "skills");
|
|
3885
|
+
const skillFiles = listFiles(skillsRoot).filter((file) => file.endsWith(`${path9.sep}SKILL.md`));
|
|
3886
|
+
const skillDirectories = fs9.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
|
|
3448
3887
|
check(checks, "skills", skillFiles.length > 0 && skillFiles.length === skillDirectories, `${skillFiles.length}/${skillDirectories} skill entrypoints`);
|
|
3449
|
-
const agentRoot =
|
|
3888
|
+
const agentRoot = path9.join(projectRoot, "plugins", "codex-agent", "agents");
|
|
3450
3889
|
check(checks, "plugin-agents", listFiles(agentRoot).filter((file) => file.endsWith(".md")).length === agentProfiles.length, `${agentProfiles.length} canonical profiles in ${agentRoot}`);
|
|
3451
|
-
const hooks =
|
|
3452
|
-
check(checks, "hooks",
|
|
3890
|
+
const hooks = path9.join(projectRoot, "plugins", "codex-agent", "hooks", "hooks.json");
|
|
3891
|
+
check(checks, "hooks", fs9.existsSync(hooks) && !parseJson(hooks).error, hooks);
|
|
3453
3892
|
}
|
|
3454
3893
|
return { root: projectRoot, ok: checks.every((item) => item.ok), checks };
|
|
3455
3894
|
};
|
|
3456
3895
|
var evaluateRouting = ({ root }) => {
|
|
3457
|
-
const projectRoot =
|
|
3458
|
-
const suitePath =
|
|
3459
|
-
if (!
|
|
3460
|
-
const suite = JSON.parse(
|
|
3461
|
-
const skillsRoot =
|
|
3896
|
+
const projectRoot = path9.resolve(root);
|
|
3897
|
+
const suitePath = path9.join(projectRoot, "evals", "skill-routing.json");
|
|
3898
|
+
if (!fs9.existsSync(suitePath)) throw new Error(`Routing suite not found: ${suitePath}`);
|
|
3899
|
+
const suite = JSON.parse(fs9.readFileSync(suitePath, "utf8"));
|
|
3900
|
+
const skillsRoot = path9.join(projectRoot, "plugins", "codex-agent", "skills");
|
|
3462
3901
|
const available = new Set(
|
|
3463
|
-
|
|
3902
|
+
fs9.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
3464
3903
|
);
|
|
3465
3904
|
const ids = /* @__PURE__ */ new Set();
|
|
3466
3905
|
const failures = [];
|
|
@@ -3498,13 +3937,13 @@ var evaluateRouting = ({ root }) => {
|
|
|
3498
3937
|
return { ok: failures.length === 0, scenarios: suite.cases?.length ?? 0, skills: available.size, byKind, failures };
|
|
3499
3938
|
};
|
|
3500
3939
|
var evaluateBehaviorContracts = ({ root }) => {
|
|
3501
|
-
const projectRoot =
|
|
3502
|
-
const suitePath =
|
|
3503
|
-
if (!
|
|
3504
|
-
const suite = JSON.parse(
|
|
3505
|
-
const skillsRoot =
|
|
3940
|
+
const projectRoot = path9.resolve(root);
|
|
3941
|
+
const suitePath = path9.join(projectRoot, "evals", "behavior-contracts.json");
|
|
3942
|
+
if (!fs9.existsSync(suitePath)) throw new Error(`Behavior suite not found: ${suitePath}`);
|
|
3943
|
+
const suite = JSON.parse(fs9.readFileSync(suitePath, "utf8"));
|
|
3944
|
+
const skillsRoot = path9.join(projectRoot, "plugins", "codex-agent", "skills");
|
|
3506
3945
|
const availableSkills = new Set(
|
|
3507
|
-
|
|
3946
|
+
fs9.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
3508
3947
|
);
|
|
3509
3948
|
const availableAgents = new Set(agentProfiles.map((profile) => profile.name));
|
|
3510
3949
|
const coveredSkills = /* @__PURE__ */ new Set();
|
|
@@ -3556,6 +3995,7 @@ Usage:
|
|
|
3556
3995
|
codex-agent context init [--root PATH] [--analysis FILE] [--apply --plan-hash HASH] [--force] [--json]
|
|
3557
3996
|
codex-agent context refresh [--root PATH] [--analysis FILE] [--apply --plan-hash HASH] [--force] [--json]
|
|
3558
3997
|
codex-agent context index [--root PATH] [--dry-run] [--json]
|
|
3998
|
+
codex-agent context lint [--root PATH] [--strict] [--json]
|
|
3559
3999
|
codex-agent context save --proposal FILE [--root PATH] [--apply] [--update] [--json]
|
|
3560
4000
|
|
|
3561
4001
|
Both init and refresh preview by default. Apply the exact reviewed preview by passing --apply with its planHash.`;
|
|
@@ -3574,6 +4014,7 @@ var FLAG_KEYS = /* @__PURE__ */ new Map([
|
|
|
3574
4014
|
["--include-templates", "includeTemplates"],
|
|
3575
4015
|
["--include-workflows", "includeWorkflows"],
|
|
3576
4016
|
["--json", "json"],
|
|
4017
|
+
["--strict", "strict"],
|
|
3577
4018
|
["--update", "update"]
|
|
3578
4019
|
]);
|
|
3579
4020
|
var parseOptions = (args, { command, options = [], flags = [] }) => {
|
|
@@ -3599,13 +4040,13 @@ var parseOptions = (args, { command, options = [], flags = [] }) => {
|
|
|
3599
4040
|
}
|
|
3600
4041
|
throw new Error(`Unknown option for ${command}: ${argument}`);
|
|
3601
4042
|
}
|
|
3602
|
-
parsed.root =
|
|
4043
|
+
parsed.root = path10.resolve(parsed.root ?? process.cwd());
|
|
3603
4044
|
return parsed;
|
|
3604
4045
|
};
|
|
3605
4046
|
var readJsonFile = (file, label) => {
|
|
3606
|
-
const absolute =
|
|
3607
|
-
if (!
|
|
3608
|
-
return JSON.parse(
|
|
4047
|
+
const absolute = path10.resolve(file);
|
|
4048
|
+
if (!fs10.existsSync(absolute)) throw new Error(`${label} file not found: ${absolute}`);
|
|
4049
|
+
return JSON.parse(fs10.readFileSync(absolute, "utf8"));
|
|
3609
4050
|
};
|
|
3610
4051
|
var write = (value, json) => {
|
|
3611
4052
|
if (json) {
|
|
@@ -3622,7 +4063,7 @@ var finishWithConflicts = (result, json) => {
|
|
|
3622
4063
|
write(result, json);
|
|
3623
4064
|
if (result.conflicts.length) process.exitCode = 2;
|
|
3624
4065
|
};
|
|
3625
|
-
var
|
|
4066
|
+
var main4 = async (args) => {
|
|
3626
4067
|
const [command, ...rest] = args;
|
|
3627
4068
|
if (!command || command === "help") {
|
|
3628
4069
|
if (rest.length === 1 && rest[0] === "context") write(contextUsage, false);
|
|
@@ -3637,7 +4078,7 @@ var main3 = async (args) => {
|
|
|
3637
4078
|
return;
|
|
3638
4079
|
}
|
|
3639
4080
|
if (contextArgs.includes("--help") || contextArgs.includes("-h")) {
|
|
3640
|
-
if (!["init", "refresh", "index", "save"].includes(subcommand)) {
|
|
4081
|
+
if (!["init", "refresh", "index", "lint", "save"].includes(subcommand)) {
|
|
3641
4082
|
throw new Error(`Unknown context command: ${subcommand}
|
|
3642
4083
|
|
|
3643
4084
|
${contextUsage}`);
|
|
@@ -3667,6 +4108,17 @@ ${contextUsage}`);
|
|
|
3667
4108
|
write({ path: result.path, entries: result.index.entries.length, dryRun: result.dryRun }, options.json);
|
|
3668
4109
|
return;
|
|
3669
4110
|
}
|
|
4111
|
+
if (subcommand === "lint") {
|
|
4112
|
+
const options = parseOptions(contextArgs, {
|
|
4113
|
+
command: "context lint",
|
|
4114
|
+
options: ["--root"],
|
|
4115
|
+
flags: ["--strict", "--json"]
|
|
4116
|
+
});
|
|
4117
|
+
const result = lintContext(options);
|
|
4118
|
+
write(result, options.json);
|
|
4119
|
+
if (!result.ok) process.exitCode = 1;
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
3670
4122
|
if (subcommand === "save") {
|
|
3671
4123
|
const options = parseOptions(contextArgs, {
|
|
3672
4124
|
command: "context save",
|
|
@@ -3741,7 +4193,7 @@ ${usage}`);
|
|
|
3741
4193
|
};
|
|
3742
4194
|
|
|
3743
4195
|
// bin/codex-agent.mjs
|
|
3744
|
-
|
|
4196
|
+
main4(process.argv.slice(2)).catch((error) => {
|
|
3745
4197
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3746
4198
|
`);
|
|
3747
4199
|
process.exitCode = 1;
|