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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/bin/cli.js +23 -0
  4. package/package.json +46 -0
  5. package/plugin/.codex-plugin/plugin.json +26 -0
  6. package/plugin/skills/api-design/SKILL.md +40 -0
  7. package/plugin/skills/api-design/agents/openai.yaml +4 -0
  8. package/plugin/skills/architecture-review/SKILL.md +40 -0
  9. package/plugin/skills/architecture-review/agents/openai.yaml +4 -0
  10. package/plugin/skills/brainstorm/SKILL.md +41 -0
  11. package/plugin/skills/brainstorm/agents/openai.yaml +4 -0
  12. package/plugin/skills/database-design/SKILL.md +40 -0
  13. package/plugin/skills/database-design/agents/openai.yaml +4 -0
  14. package/plugin/skills/debug/SKILL.md +40 -0
  15. package/plugin/skills/debug/agents/openai.yaml +4 -0
  16. package/plugin/skills/estimate/SKILL.md +40 -0
  17. package/plugin/skills/estimate/agents/openai.yaml +4 -0
  18. package/plugin/skills/fix-findings/SKILL.md +40 -0
  19. package/plugin/skills/fix-findings/agents/openai.yaml +4 -0
  20. package/plugin/skills/implement/SKILL.md +40 -0
  21. package/plugin/skills/implement/agents/openai.yaml +4 -0
  22. package/plugin/skills/migration/SKILL.md +40 -0
  23. package/plugin/skills/migration/agents/openai.yaml +4 -0
  24. package/plugin/skills/plan/SKILL.md +40 -0
  25. package/plugin/skills/plan/agents/openai.yaml +4 -0
  26. package/plugin/skills/refactor/SKILL.md +40 -0
  27. package/plugin/skills/refactor/agents/openai.yaml +4 -0
  28. package/plugin/skills/release-readiness/SKILL.md +39 -0
  29. package/plugin/skills/release-readiness/agents/openai.yaml +4 -0
  30. package/plugin/skills/review/SKILL.md +39 -0
  31. package/plugin/skills/review/agents/openai.yaml +4 -0
  32. package/plugin/skills/security-review/SKILL.md +39 -0
  33. package/plugin/skills/security-review/agents/openai.yaml +4 -0
  34. package/plugin/skills/test/SKILL.md +40 -0
  35. package/plugin/skills/test/agents/openai.yaml +4 -0
  36. package/scripts/validate.mjs +29 -0
  37. package/src/cli.js +85 -0
  38. package/src/codex.js +13 -0
  39. package/src/commands.js +98 -0
  40. package/src/doctor.js +40 -0
  41. package/src/fs-safe.js +38 -0
  42. package/src/plugin-install.js +135 -0
  43. package/src/plugin-state.js +82 -0
  44. package/src/project-init.js +53 -0
  45. package/src/uninstall.js +50 -0
  46. package/templates/AGENTS.md +48 -0
  47. package/templates/docs/agent/decision-log.md +15 -0
  48. package/templates/docs/agent/findings.md +13 -0
  49. package/templates/docs/agent/handoff.md +20 -0
  50. package/templates/docs/agent/plan.md +14 -0
  51. 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,4 @@
1
+ interface:
2
+ display_name: "Migration"
3
+ short_description: "Plan reversible migrations with validation and rollback"
4
+ default_prompt: "Use $migration to design a staged, observable, and recoverable transition."
@@ -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,4 @@
1
+ interface:
2
+ display_name: "Plan"
3
+ short_description: "Turn complex work into a verifiable execution plan"
4
+ default_prompt: "Use $plan to turn this task into a concise, verifiable execution plan."
@@ -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,4 @@
1
+ interface:
2
+ display_name: "Refactor"
3
+ short_description: "Improve internal structure while preserving behavior"
4
+ default_prompt: "Use $refactor to improve this structure while preserving observable behavior."
@@ -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,4 @@
1
+ interface:
2
+ display_name: "Release Readiness"
3
+ short_description: "Judge readiness from current verification evidence"
4
+ default_prompt: "Use $release-readiness to decide whether this candidate is ready and identify every blocker."
@@ -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,4 @@
1
+ interface:
2
+ display_name: "Review"
3
+ short_description: "Find actionable defects in a code change or fix"
4
+ default_prompt: "Use $review to find evidence-backed defects in this change without editing it."
@@ -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,4 @@
1
+ interface:
2
+ display_name: "Security Review"
3
+ short_description: "Assess trust boundaries and evidence-backed security risk"
4
+ default_prompt: "Use $security-review to assess this scope for plausible, evidence-backed security weaknesses."
@@ -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,4 @@
1
+ interface:
2
+ display_name: "Test"
3
+ short_description: "Design focused tests for behavior and regression risk"
4
+ default_prompt: "Use $test to prove the important behavior and regression risks in this scope."
@@ -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
+ }
@@ -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
+ }