nono-skills 0.1.0
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/LICENSE +21 -0
- package/README.md +127 -0
- package/bin/cli.js +23 -0
- package/package.json +46 -0
- package/plugin/.codex-plugin/plugin.json +26 -0
- package/plugin/skills/api-design/SKILL.md +40 -0
- package/plugin/skills/api-design/agents/openai.yaml +4 -0
- package/plugin/skills/architecture-review/SKILL.md +40 -0
- package/plugin/skills/architecture-review/agents/openai.yaml +4 -0
- package/plugin/skills/brainstorm/SKILL.md +41 -0
- package/plugin/skills/brainstorm/agents/openai.yaml +4 -0
- package/plugin/skills/database-design/SKILL.md +40 -0
- package/plugin/skills/database-design/agents/openai.yaml +4 -0
- package/plugin/skills/debug/SKILL.md +40 -0
- package/plugin/skills/debug/agents/openai.yaml +4 -0
- package/plugin/skills/estimate/SKILL.md +40 -0
- package/plugin/skills/estimate/agents/openai.yaml +4 -0
- package/plugin/skills/fix-findings/SKILL.md +40 -0
- package/plugin/skills/fix-findings/agents/openai.yaml +4 -0
- package/plugin/skills/implement/SKILL.md +40 -0
- package/plugin/skills/implement/agents/openai.yaml +4 -0
- package/plugin/skills/migration/SKILL.md +40 -0
- package/plugin/skills/migration/agents/openai.yaml +4 -0
- package/plugin/skills/plan/SKILL.md +40 -0
- package/plugin/skills/plan/agents/openai.yaml +4 -0
- package/plugin/skills/refactor/SKILL.md +40 -0
- package/plugin/skills/refactor/agents/openai.yaml +4 -0
- package/plugin/skills/release-readiness/SKILL.md +39 -0
- package/plugin/skills/release-readiness/agents/openai.yaml +4 -0
- package/plugin/skills/review/SKILL.md +39 -0
- package/plugin/skills/review/agents/openai.yaml +4 -0
- package/plugin/skills/security-review/SKILL.md +39 -0
- package/plugin/skills/security-review/agents/openai.yaml +4 -0
- package/plugin/skills/test/SKILL.md +40 -0
- package/plugin/skills/test/agents/openai.yaml +4 -0
- package/scripts/validate.mjs +29 -0
- package/src/cli.js +85 -0
- package/src/codex.js +13 -0
- package/src/commands.js +98 -0
- package/src/doctor.js +40 -0
- package/src/fs-safe.js +38 -0
- package/src/plugin-install.js +135 -0
- package/src/plugin-state.js +82 -0
- package/src/project-init.js +53 -0
- package/src/uninstall.js +50 -0
- package/templates/AGENTS.md +48 -0
- package/templates/docs/agent/decision-log.md +15 -0
- package/templates/docs/agent/findings.md +13 -0
- package/templates/docs/agent/handoff.md +20 -0
- package/templates/docs/agent/plan.md +14 -0
- package/templates/docs/agent/spec.md +20 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migration
|
|
3
|
+
description: Use when data, schema, API, dependency, platform, infrastructure, or runtime state must move between versions or systems while preserving service and recoverability.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Migration
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Design or execute a staged, observable, reversible transition with explicit compatibility windows, validation, and cleanup.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- Source and target states, invariants, data volume, traffic, and downtime tolerance
|
|
15
|
+
- Producers, consumers, schemas, deployment topology, ownership, and operational constraints
|
|
16
|
+
- Backfill, rollback, compliance, and retention requirements
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
- Migration phases, compatibility strategy, validation, monitoring, rollback, and cleanup criteria
|
|
21
|
+
- If execution is authorized: migrations, adapters, backfills, tests, and runbook changes
|
|
22
|
+
- Reconciliation evidence and remaining risk
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Inspect all readers and writers before changing shared state.
|
|
27
|
+
- Prefer expand-migrate-contract for live systems: introduce compatibility, move traffic or data, verify, then remove old paths.
|
|
28
|
+
- Make reruns idempotent and define partial-failure recovery.
|
|
29
|
+
- Validate counts plus business invariants; do not treat command success as data correctness.
|
|
30
|
+
- Separate source implementation from production execution authorization.
|
|
31
|
+
- Preserve backups and rollback paths appropriate to impact.
|
|
32
|
+
|
|
33
|
+
## Decision-log updates
|
|
34
|
+
|
|
35
|
+
Record compatibility windows, cutover criteria, data ownership, transformation rules, rollback limits, accepted downtime, and irreversible checkpoints. Link evidence for completing each phase.
|
|
36
|
+
Use an existing `docs/agent/decision-log.md`. If it is absent, include the decision in the final response; create workflow artifacts only when the user requests them.
|
|
37
|
+
|
|
38
|
+
## Escalate to the human
|
|
39
|
+
|
|
40
|
+
Escalate before destructive or irreversible steps, production backfills or cutovers, downtime, data loss or semantic ambiguity, privacy-sensitive movement, or rollback beyond the stated tolerance.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan
|
|
3
|
+
description: Use when a software direction is defined enough to map multi-step, cross-cutting, risky, or long-running work into a concrete execution plan before implementation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plan
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Turn the request and repository evidence into a decision-ready spec and a concise, verifiable execution map. Plan to the level warranted by risk; do not add ceremony to trivial work.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- User goal, constraints, and expected behavior
|
|
15
|
+
- Applicable `AGENTS.md`, current code, tests, docs, issues, and runtime evidence
|
|
16
|
+
- Existing `docs/agent/spec.md`, `plan.md`, and `decision-log.md`
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
- A decision-ready spec and concise execution map with testable acceptance criteria
|
|
21
|
+
- When the artifact files already exist or the user requests durable planning, updated `docs/agent/spec.md` and `docs/agent/plan.md`
|
|
22
|
+
- A short summary of assumptions and human decisions needed
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Inspect the real code path before decomposing implementation work.
|
|
27
|
+
- Separate confirmed facts, inferences, and unresolved choices.
|
|
28
|
+
- Keep work items outcome-based and independently verifiable; avoid file-by-file pseudo-instructions.
|
|
29
|
+
- Include compatibility, migration, rollout, observability, and rollback work only when relevant.
|
|
30
|
+
- Do not edit production code while the request is planning-only.
|
|
31
|
+
- Re-plan when new evidence invalidates a premise rather than defending the old plan.
|
|
32
|
+
|
|
33
|
+
## Decision-log updates
|
|
34
|
+
|
|
35
|
+
Record scope interpretations, contract choices, rejected approaches with non-obvious tradeoffs, and material re-plans. Link each entry to the affected plan item.
|
|
36
|
+
Use an existing `docs/agent/decision-log.md`. If it is absent, include the decision in the final response; create workflow artifacts only when the user requests them.
|
|
37
|
+
|
|
38
|
+
## Escalate to the human
|
|
39
|
+
|
|
40
|
+
Escalate when competing interpretations change user-visible behavior, a required system or owner is outside scope, risk cannot be bounded, or the plan requires destructive operations, production access, significant spend, or a product decision. Otherwise state reasonable assumptions and proceed.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refactor
|
|
3
|
+
description: Use when internal code structure, naming, duplication, coupling, complexity, or module boundaries should improve without changing externally observable behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Refactor
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Improve changeability and clarity while preserving behavior and keeping the diff proportionate to a concrete maintenance problem.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- Refactoring goal or documented code smell
|
|
15
|
+
- Existing behavior, public contracts, tests, dependency graph, and performance constraints
|
|
16
|
+
- Current repository conventions
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
- Focused structural changes with stable or improved tests
|
|
21
|
+
- Before-and-after explanation tied to the maintenance goal
|
|
22
|
+
- Verification that behavior and relevant performance remain acceptable
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Establish a behavioral safety net before structural change; add characterization tests when needed.
|
|
27
|
+
- Separate behavior changes from refactoring so reviewers can reason about each.
|
|
28
|
+
- Optimize for deeper modules, clear ownership, and reduced knowledge leakage, not smaller files by default.
|
|
29
|
+
- Do not create abstractions without repeated variation or a proven boundary.
|
|
30
|
+
- Preserve public APIs, persistence formats, error semantics, and ordering unless explicitly authorized.
|
|
31
|
+
- Measure rather than assume performance-sensitive equivalence.
|
|
32
|
+
|
|
33
|
+
## Decision-log updates
|
|
34
|
+
|
|
35
|
+
Record new module boundaries, ownership changes, intentionally retained debt, compatibility constraints, and abstraction choices with long-term consequences. Routine renames and local cleanup do not need entries.
|
|
36
|
+
Use an existing `docs/agent/decision-log.md`. If it is absent, include the decision in the final response; create workflow artifacts only when the user requests them.
|
|
37
|
+
|
|
38
|
+
## Escalate to the human
|
|
39
|
+
|
|
40
|
+
Escalate when preserving behavior is impossible or unprovable, the refactor implies a public or data contract change, scope grows across ownership boundaries, or the safety net is too weak for the risk.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-readiness
|
|
3
|
+
description: Use when a change, branch, build, migration, or release candidate must be assessed before merge, deployment, rollout, or handoff.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Release Readiness
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Produce an evidence-backed `ready`, `not ready`, or `ready with accepted risks` verdict. Assessment does not authorize release actions.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- Release scope, target environment, acceptance criteria, and rollout expectations
|
|
15
|
+
- Current worktree or candidate, CI results, tests, findings, migrations, docs, and operational signals
|
|
16
|
+
- Rollback, observability, compatibility, and ownership information
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
- Verdict with blockers, accepted risks, and evidence
|
|
21
|
+
- Checklist covering code quality, tests, build, security, data change, compatibility, observability, rollback, docs, and support readiness as applicable
|
|
22
|
+
- Exact unverified items and required next actions
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Inspect current state; do not rely on stale claims that checks passed.
|
|
27
|
+
- Run the strongest safe local checks and distinguish local proof from CI, staging, and production proof.
|
|
28
|
+
- Treat code complete, merge ready, deploy ready, deployed, and verified in production as separate states.
|
|
29
|
+
- Block on open critical or high findings unless risk is explicitly accepted by the proper owner.
|
|
30
|
+
- Do not merge, tag, publish, deploy, or migrate without explicit authorization.
|
|
31
|
+
|
|
32
|
+
## Decision-log updates
|
|
33
|
+
|
|
34
|
+
Record risk acceptance, waived gates, rollout or rollback choices, and readiness verdict changes with evidence. Link unresolved findings and owners.
|
|
35
|
+
Use an existing `docs/agent/decision-log.md`. If it is absent, include the decision in the final response; create workflow artifacts only when the user requests them.
|
|
36
|
+
|
|
37
|
+
## Escalate to the human
|
|
38
|
+
|
|
39
|
+
Escalate for blocked gates, missing rollback for high-risk changes, unresolved ownership, destructive migration, compliance or security risk, production action, or any request to waive a required control.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: Use for a general objective code, diff, PR, or fix review without edits; use security-review when security is the primary assessment objective.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Review
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Find actionable correctness, compatibility, security, reliability, and maintainability defects in the requested change or scope. Review is read-only.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- Review baseline and target: diff, commit, branch, files, or stated behavior
|
|
15
|
+
- Requirements, acceptance criteria, applicable instructions, tests, and runtime evidence
|
|
16
|
+
- Existing findings and decision records
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
- Findings ordered `critical`, `high`, `medium`, then `low`
|
|
21
|
+
- Each finding includes location, evidence, impact, reasoning or reproduction, and remediation direction
|
|
22
|
+
- Residual risks and verification gaps; explicitly state when no actionable findings remain
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Establish the exact review boundary and inspect the current artifact, not an assumed stale state.
|
|
27
|
+
- Trace affected runtime and data paths beyond the diff when needed to prove impact.
|
|
28
|
+
- Prioritize defects over style preferences. Do not invent issues for report completeness.
|
|
29
|
+
- Distinguish verified defects from questions and speculative risks.
|
|
30
|
+
- Do not modify code or mark a finding fixed without evidence.
|
|
31
|
+
|
|
32
|
+
## Decision-log updates
|
|
33
|
+
|
|
34
|
+
Record only review-scope interpretations or accepted risk decisions that future work must preserve. Put individual defects in an existing `docs/agent/findings.md`; otherwise report them in the final response.
|
|
35
|
+
Use an existing `docs/agent/decision-log.md`. If it is absent, include the decision in the final response; create workflow artifacts only when the user requests them.
|
|
36
|
+
|
|
37
|
+
## Escalate to the human
|
|
38
|
+
|
|
39
|
+
Escalate when the baseline is ambiguous and changes the verdict, required evidence is inaccessible, a suspected critical issue could expose users or data, or the review requires live destructive testing. Otherwise complete the strongest safe review and disclose the gap.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-review
|
|
3
|
+
description: Use when security is the primary assessment objective for code, configuration, architecture, identities, data handling, or a change set.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Security Review
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Identify plausible, evidence-backed security weaknesses across trust boundaries, attacker-controlled inputs, identities, secrets, data, and operational controls. Review is read-only.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- Defined scope, threat assumptions, and deployment context
|
|
15
|
+
- Code, dependencies, configuration, infrastructure, schemas, and auth flows
|
|
16
|
+
- Existing security findings, tests, and compensating controls
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
- Findings ordered by exploitability and impact, with evidence and attack path
|
|
21
|
+
- Affected assets, preconditions, remediation direction, and confidence
|
|
22
|
+
- Coverage limits and residual risks
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Map trust boundaries and attacker capabilities before applying checklists.
|
|
27
|
+
- Check authentication, authorization, injection, XSS, CSRF, SSRF, secret handling, cryptography, data exposure, dependency risk, abuse controls, and logging where relevant.
|
|
28
|
+
- Never include live secrets, exploit unrelated systems, or perform destructive testing.
|
|
29
|
+
- Distinguish confirmed vulnerabilities from hardening opportunities.
|
|
30
|
+
- Do not edit code unless the user separately requests remediation.
|
|
31
|
+
|
|
32
|
+
## Decision-log updates
|
|
33
|
+
|
|
34
|
+
Keep vulnerabilities in an existing `docs/agent/findings.md`; otherwise report sanitized findings in the final response. Log only accepted security tradeoffs, threat-model assumptions, compensating controls, or risk acceptance decisions.
|
|
35
|
+
Use an existing `docs/agent/decision-log.md`. If it is absent, include the decision in the final response; create workflow artifacts only when the user requests them.
|
|
36
|
+
|
|
37
|
+
## Escalate to the human
|
|
38
|
+
|
|
39
|
+
Escalate immediately for plausible critical exposure, active compromise indicators, secrets in version control, testing that may affect real users, or remediation requiring risk acceptance. Avoid broadcasting sensitive details beyond the authorized audience.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test
|
|
3
|
+
description: Use when software behavior needs new or improved unit, integration, contract, end-to-end, edge-case, or regression tests, or when test coverage must be assessed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Create the smallest reliable test set that proves important behavior and failure modes through stable public seams.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- Behavioral contract, bug report, acceptance criteria, or changed code
|
|
15
|
+
- Existing test architecture, fixtures, commands, and coverage gaps
|
|
16
|
+
- Relevant runtime, data, and external-system boundaries
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
- Focused automated tests and minimal necessary test utilities
|
|
21
|
+
- Exact commands and results
|
|
22
|
+
- Documented gaps that cannot be tested safely or deterministically
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Prefer behavior assertions over implementation-detail assertions.
|
|
27
|
+
- For a bug, first demonstrate the failure when practical, then verify the fix.
|
|
28
|
+
- Cover happy path, meaningful boundaries, errors, permissions, and regression risk in proportion to impact.
|
|
29
|
+
- Use real components where practical; mock only unstable or external boundaries.
|
|
30
|
+
- Eliminate flaky timing, shared-state leakage, order dependence, and production dependencies.
|
|
31
|
+
- Do not change production semantics merely to make a weak test pass.
|
|
32
|
+
|
|
33
|
+
## Decision-log updates
|
|
34
|
+
|
|
35
|
+
Record material test-boundary choices, intentionally excluded coverage, chosen substitutes for unavailable dependencies, and risk accepted because a scenario cannot be automated. Routine test cases do not need entries.
|
|
36
|
+
Use an existing `docs/agent/decision-log.md`. If it is absent, include the decision in the final response; create workflow artifacts only when the user requests them.
|
|
37
|
+
|
|
38
|
+
## Escalate to the human
|
|
39
|
+
|
|
40
|
+
Escalate when validation requires production data or destructive actions, expected behavior is undefined, the available environment cannot provide meaningful proof, or the requested test conflicts with privacy, cost, or reliability constraints.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { listFiles } from '../src/fs-safe.js';
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(import.meta.dirname, '..');
|
|
8
|
+
const packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
|
|
9
|
+
const plugin = JSON.parse(await readFile(path.join(root, 'plugin', '.codex-plugin', 'plugin.json'), 'utf8'));
|
|
10
|
+
assert.equal(plugin.name, 'engineering');
|
|
11
|
+
assert.equal(plugin.version, packageJson.version);
|
|
12
|
+
|
|
13
|
+
const skillRoot = path.join(root, 'plugin', 'skills');
|
|
14
|
+
const skillFiles = (await listFiles(skillRoot)).filter((file) => file.endsWith('/SKILL.md'));
|
|
15
|
+
assert.equal(skillFiles.length, 15);
|
|
16
|
+
for (const relative of skillFiles) {
|
|
17
|
+
const expectedName = relative.split(path.sep)[0];
|
|
18
|
+
const content = await readFile(path.join(skillRoot, relative), 'utf8');
|
|
19
|
+
const metadata = await readFile(path.join(skillRoot, expectedName, 'agents', 'openai.yaml'), 'utf8');
|
|
20
|
+
const shortDescription = metadata.match(/short_description: "([^"]+)"/)?.[1];
|
|
21
|
+
assert.match(content, new RegExp(`^---\\nname: ${expectedName}\\ndescription: .+\\n---`, 's'));
|
|
22
|
+
assert.doesNotMatch(content, /TODO|Superpowers|\.codex\/skills/);
|
|
23
|
+
assert.match(content, /create workflow artifacts only when the user requests them/);
|
|
24
|
+
assert.ok(shortDescription && shortDescription.length >= 25 && shortDescription.length <= 64);
|
|
25
|
+
assert.doesNotMatch(metadata, /Reusable engineering workflow|for this task\./);
|
|
26
|
+
assert.match(metadata, new RegExp(`default_prompt: ".*\\$${expectedName.replaceAll('-', '\\-')}\\b`));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`Validated engineering plugin ${plugin.version} with ${skillFiles.length} skills.`);
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const COMMANDS = new Set(['install', 'init', 'update', 'doctor', 'uninstall']);
|
|
2
|
+
|
|
3
|
+
export const HELP = `nono-skills <command> [options]
|
|
4
|
+
|
|
5
|
+
Commands:
|
|
6
|
+
install Install the engineering Codex plugin
|
|
7
|
+
init [directory] Add project guidance and agent artifacts
|
|
8
|
+
update Update an owned plugin installation
|
|
9
|
+
doctor Diagnose the plugin installation
|
|
10
|
+
uninstall Remove the owned plugin installation
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--dry-run Show project changes without writing
|
|
14
|
+
--force Back up and replace conflicting project files
|
|
15
|
+
--purge-project <path> Remove unchanged installed project artifacts
|
|
16
|
+
--version Print package version
|
|
17
|
+
--help Show this help
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export function parseArgs(argv) {
|
|
21
|
+
const result = {
|
|
22
|
+
command: 'help', target: undefined, force: false, dryRun: false,
|
|
23
|
+
purgeProject: undefined, help: false, version: false,
|
|
24
|
+
};
|
|
25
|
+
const args = [...argv];
|
|
26
|
+
if (args.length === 0) {
|
|
27
|
+
result.help = true;
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
if (args[0] === '--version') {
|
|
31
|
+
result.command = 'version';
|
|
32
|
+
result.version = true;
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
36
|
+
result.help = true;
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
result.command = args.shift();
|
|
40
|
+
while (args.length) {
|
|
41
|
+
const arg = args.shift();
|
|
42
|
+
if (arg === '--force') result.force = true;
|
|
43
|
+
else if (arg === '--dry-run') result.dryRun = true;
|
|
44
|
+
else if (arg === '--help' || arg === '-h') result.help = true;
|
|
45
|
+
else if (arg === '--purge-project') result.purgeProject = args.shift();
|
|
46
|
+
else if (!arg.startsWith('-') && result.command === 'init' && result.target === undefined) result.target = arg;
|
|
47
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function run(argv, context) {
|
|
53
|
+
const stdout = context.stdout ?? process.stdout;
|
|
54
|
+
const stderr = context.stderr ?? process.stderr;
|
|
55
|
+
let options;
|
|
56
|
+
try {
|
|
57
|
+
options = parseArgs(argv);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
stderr.write(`${error.message}\n`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
if (options.version) {
|
|
63
|
+
stdout.write(`${context.version ?? '0.1.0'}\n`);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
if (options.help) {
|
|
67
|
+
stdout.write(HELP);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
if (!COMMANDS.has(options.command)) {
|
|
71
|
+
stderr.write(`Unknown command: ${options.command}\n`);
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
const handler = context.handlers?.[options.command];
|
|
75
|
+
if (!handler) {
|
|
76
|
+
stderr.write(`Command is not available: ${options.command}\n`);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return await handler(options, context);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
stderr.write(`${error.message}\n`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/codex.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function runCodex(args, { env = process.env, cwd = process.cwd() } = {}) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const child = spawn('codex', args, { env, cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
6
|
+
let stdout = '';
|
|
7
|
+
let stderr = '';
|
|
8
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
9
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
10
|
+
child.on('error', (error) => resolve({ code: 127, stdout, stderr: error.message }));
|
|
11
|
+
child.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
12
|
+
});
|
|
13
|
+
}
|
package/src/commands.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readFile, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { runCodex as defaultRunCodex } from './codex.js';
|
|
5
|
+
import { diagnose } from './doctor.js';
|
|
6
|
+
import { listFiles, sha256File } from './fs-safe.js';
|
|
7
|
+
import { installPlugin, updatePlugin } from './plugin-install.js';
|
|
8
|
+
import { writeJsonAtomic } from './plugin-state.js';
|
|
9
|
+
import { applyProjectInit, planProjectInit } from './project-init.js';
|
|
10
|
+
import { purgeProject, uninstallPlugin } from './uninstall.js';
|
|
11
|
+
|
|
12
|
+
async function exists(file) {
|
|
13
|
+
try { await stat(file); return true; }
|
|
14
|
+
catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function timestamp() {
|
|
18
|
+
return new Date().toISOString().replaceAll(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createHandlers(base) {
|
|
22
|
+
const stdout = base.stdout ?? process.stdout;
|
|
23
|
+
const stderr = base.stderr ?? process.stderr;
|
|
24
|
+
const runCodex = base.runCodex ?? defaultRunCodex;
|
|
25
|
+
const removePlugin = base.uninstallPlugin ?? uninstallPlugin;
|
|
26
|
+
const pluginContext = () => ({
|
|
27
|
+
home: base.home,
|
|
28
|
+
packageRoot: base.packageRoot,
|
|
29
|
+
packageVersion: base.packageVersion,
|
|
30
|
+
runCodex,
|
|
31
|
+
clock: base.clock ?? timestamp,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
async install() {
|
|
36
|
+
const result = await installPlugin(pluginContext());
|
|
37
|
+
stdout.write(`Engineering plugin ${result.status} (${result.pluginVersion}). Start a new Codex task to activate it.\n`);
|
|
38
|
+
return 0;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async update() {
|
|
42
|
+
const result = await updatePlugin(pluginContext());
|
|
43
|
+
stdout.write(`Engineering plugin ${result.status} (${result.pluginVersion}). Start a new Codex task to activate it.\n`);
|
|
44
|
+
return 0;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async init(options) {
|
|
48
|
+
const targetRoot = path.resolve(base.cwd, options.target ?? '.');
|
|
49
|
+
const templateRoot = path.join(base.packageRoot, 'templates');
|
|
50
|
+
const actions = await planProjectInit({
|
|
51
|
+
templateRoot, targetRoot, force: options.force, dryRun: options.dryRun, clock: base.clock ?? timestamp,
|
|
52
|
+
});
|
|
53
|
+
const conflicts = actions.filter((action) => action.type === 'conflict');
|
|
54
|
+
if (conflicts.length) {
|
|
55
|
+
stderr.write(`Conflicts detected; no files written:\n${conflicts.map((action) => `- ${action.relative}`).join('\n')}\nUse --force to back up and replace them.\n`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
const results = await applyProjectInit(actions);
|
|
59
|
+
if (!options.dryRun) {
|
|
60
|
+
const files = await listFiles(templateRoot);
|
|
61
|
+
const checksums = {};
|
|
62
|
+
for (const relative of files) checksums[relative] = await sha256File(path.join(targetRoot, relative));
|
|
63
|
+
await writeJsonAtomic(path.join(targetRoot, '.codex-engineering-skills.json'), {
|
|
64
|
+
schemaVersion: 1, packageVersion: base.packageVersion, files: checksums,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const count = (type) => results.filter((result) => result.type === type).length;
|
|
68
|
+
stdout.write(`${options.dryRun ? 'Would create' : 'Created'} ${count('create')}, replaced ${count('replace')}, skipped ${count('skip')}.\n`);
|
|
69
|
+
return 0;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async doctor() {
|
|
73
|
+
const checks = await diagnose({ home: base.home, packageVersion: base.packageVersion, runCodex });
|
|
74
|
+
for (const check of checks) stdout.write(`${check.status.toUpperCase()} ${check.name}: ${check.detail}\n`);
|
|
75
|
+
return checks.some((check) => check.status === 'fail') ? 1 : 0;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
async uninstall(options) {
|
|
79
|
+
let purgeState;
|
|
80
|
+
let purgeTarget;
|
|
81
|
+
if (options.purgeProject) {
|
|
82
|
+
purgeTarget = path.resolve(base.cwd, options.purgeProject);
|
|
83
|
+
const statePath = path.join(purgeTarget, '.codex-engineering-skills.json');
|
|
84
|
+
if (!await exists(statePath)) throw new Error(`Project ownership state not found: ${statePath}`);
|
|
85
|
+
purgeState = JSON.parse(await readFile(statePath, 'utf8'));
|
|
86
|
+
}
|
|
87
|
+
await removePlugin({ home: base.home, runCodex });
|
|
88
|
+
stdout.write('Engineering plugin uninstalled. Project artifacts were preserved.\n');
|
|
89
|
+
if (purgeState) {
|
|
90
|
+
const statePath = path.join(purgeTarget, '.codex-engineering-skills.json');
|
|
91
|
+
const result = await purgeProject({ targetRoot: purgeTarget, recordedChecksums: purgeState.files });
|
|
92
|
+
if (result.preserved.length === 0) await rm(statePath);
|
|
93
|
+
stdout.write(`Purged ${result.removed.length}; preserved ${result.preserved.length} modified files.\n`);
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
package/src/doctor.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { listFiles } from './fs-safe.js';
|
|
5
|
+
import { verifyOwnership } from './plugin-state.js';
|
|
6
|
+
|
|
7
|
+
async function exists(file) {
|
|
8
|
+
try { await stat(file); return true; }
|
|
9
|
+
catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function diagnose({ home, packageVersion, runCodex }) {
|
|
13
|
+
const checks = [];
|
|
14
|
+
const codex = await runCodex(['--version']);
|
|
15
|
+
checks.push({ name: 'codex', status: codex.code === 0 ? 'pass' : 'fail', detail: codex.stdout || codex.stderr || 'unavailable' });
|
|
16
|
+
|
|
17
|
+
const pluginRoot = path.join(home, 'plugins', 'engineering');
|
|
18
|
+
const statePath = path.join(pluginRoot, '.installer-state.json');
|
|
19
|
+
const sourceExists = await exists(pluginRoot);
|
|
20
|
+
checks.push({ name: 'source', status: sourceExists ? 'pass' : 'fail', detail: sourceExists ? pluginRoot : 'plugin source missing' });
|
|
21
|
+
|
|
22
|
+
if (!sourceExists || !await exists(statePath)) {
|
|
23
|
+
checks.push({ name: 'ownership', status: 'fail', detail: 'installer ownership manifest missing' });
|
|
24
|
+
checks.push({ name: 'version', status: 'fail', detail: 'version unavailable' });
|
|
25
|
+
checks.push({ name: 'skills', status: 'fail', detail: 'skills unavailable' });
|
|
26
|
+
} else {
|
|
27
|
+
const state = JSON.parse(await readFile(statePath, 'utf8'));
|
|
28
|
+
const ownership = await verifyOwnership(state, pluginRoot);
|
|
29
|
+
checks.push({ name: 'ownership', status: ownership.valid ? 'pass' : 'fail', detail: ownership.valid ? 'checksums match' : `changed: ${ownership.mismatches.join(', ')}` });
|
|
30
|
+
checks.push({ name: 'version', status: state.packageVersion === packageVersion ? 'pass' : 'warn', detail: `installed ${state.packageVersion}; package ${packageVersion}` });
|
|
31
|
+
const files = await listFiles(path.join(pluginRoot, 'skills'));
|
|
32
|
+
const count = files.filter((file) => file.endsWith('/SKILL.md') || file === 'SKILL.md').length;
|
|
33
|
+
checks.push({ name: 'skills', status: count === 15 ? 'pass' : 'fail', detail: `${count} skills found` });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const registration = codex.code === 0 ? await runCodex(['plugin', 'list']) : codex;
|
|
37
|
+
const registered = registration.code === 0 && registration.stdout.includes('engineering@');
|
|
38
|
+
checks.push({ name: 'registration', status: registered ? 'pass' : 'fail', detail: registered ? 'installed and visible' : registration.stderr || 'not registered' });
|
|
39
|
+
return checks;
|
|
40
|
+
}
|
package/src/fs-safe.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const IGNORED_FILE_NAMES = new Set(['.DS_Store']);
|
|
6
|
+
|
|
7
|
+
export async function listFiles(root, relative = '') {
|
|
8
|
+
const directory = path.join(root, relative);
|
|
9
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
10
|
+
const files = [];
|
|
11
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
12
|
+
if (IGNORED_FILE_NAMES.has(entry.name)) continue;
|
|
13
|
+
const child = path.join(relative, entry.name);
|
|
14
|
+
if (entry.isDirectory()) files.push(...await listFiles(root, child));
|
|
15
|
+
else if (entry.isFile()) files.push(child);
|
|
16
|
+
}
|
|
17
|
+
return files;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function sha256File(file) {
|
|
21
|
+
const content = await readFile(file);
|
|
22
|
+
return createHash('sha256').update(content).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function filesEqual(left, right) {
|
|
26
|
+
try {
|
|
27
|
+
return await sha256File(left) === await sha256File(right);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT') return false;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function writeFileAtomic(file, content) {
|
|
35
|
+
const temporary = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
36
|
+
await writeFile(temporary, content, { mode: 0o600 });
|
|
37
|
+
await rename(temporary, file);
|
|
38
|
+
}
|