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,135 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, rename, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { listFiles } from './fs-safe.js';
|
|
5
|
+
import {
|
|
6
|
+
createOwnershipManifest, readMarketplace, upsertMarketplaceEntry,
|
|
7
|
+
verifyOwnership, writeJsonAtomic,
|
|
8
|
+
} from './plugin-state.js';
|
|
9
|
+
|
|
10
|
+
const PLUGIN_NAME = 'engineering';
|
|
11
|
+
const MARKETPLACE_ENTRY = {
|
|
12
|
+
name: PLUGIN_NAME,
|
|
13
|
+
source: { source: 'local', path: './plugins/engineering' },
|
|
14
|
+
policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' },
|
|
15
|
+
category: 'Developer Tools',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function exists(file) {
|
|
19
|
+
try { await stat(file); return true; }
|
|
20
|
+
catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readJson(file) {
|
|
24
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function withCachebuster(version, cachebuster) {
|
|
28
|
+
return `${version.split('+')[0]}+codex.${cachebuster}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function transact(context, requireExisting) {
|
|
32
|
+
const { home, packageRoot, runCodex, packageVersion } = context;
|
|
33
|
+
const cachebuster = context.clock();
|
|
34
|
+
const preflight = await runCodex(['--version']);
|
|
35
|
+
if (preflight.code !== 0) throw new Error(`Codex CLI is unavailable: ${preflight.stderr || 'version check failed'}`);
|
|
36
|
+
|
|
37
|
+
const pluginsRoot = path.join(home, 'plugins');
|
|
38
|
+
const destination = path.join(pluginsRoot, PLUGIN_NAME);
|
|
39
|
+
const statePath = path.join(destination, '.installer-state.json');
|
|
40
|
+
const marketplacePath = path.join(home, '.agents', 'plugins', 'marketplace.json');
|
|
41
|
+
const oldMarketplace = await readMarketplace(marketplacePath);
|
|
42
|
+
const destinationExists = await exists(destination);
|
|
43
|
+
if (requireExisting && !destinationExists) throw new Error('Engineering plugin is not installed');
|
|
44
|
+
|
|
45
|
+
let oldState;
|
|
46
|
+
if (destinationExists) {
|
|
47
|
+
if (!await exists(statePath)) throw new Error('Existing engineering plugin is not owned by this installer');
|
|
48
|
+
oldState = await readJson(statePath);
|
|
49
|
+
const ownership = await verifyOwnership(oldState, destination);
|
|
50
|
+
if (!ownership.valid) throw new Error(`Existing engineering plugin is not owned safely; changed files: ${ownership.mismatches.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await mkdir(pluginsRoot, { recursive: true });
|
|
54
|
+
const stage = path.join(pluginsRoot, `.engineering-stage-${process.pid}-${Date.now()}`);
|
|
55
|
+
const backup = path.join(pluginsRoot, `.engineering-backup-${process.pid}-${Date.now()}`);
|
|
56
|
+
let movedExisting = false;
|
|
57
|
+
let installedStage = false;
|
|
58
|
+
let registrationSucceeded = false;
|
|
59
|
+
let registrationMarketplace = oldMarketplace?.name ?? 'personal';
|
|
60
|
+
try {
|
|
61
|
+
await cp(path.join(packageRoot, 'plugin'), stage, { recursive: true, errorOnExist: true });
|
|
62
|
+
const pluginJsonPath = path.join(stage, '.codex-plugin', 'plugin.json');
|
|
63
|
+
const plugin = await readJson(pluginJsonPath);
|
|
64
|
+
plugin.version = withCachebuster(plugin.version, cachebuster);
|
|
65
|
+
await writeJsonAtomic(pluginJsonPath, plugin);
|
|
66
|
+
const files = await listFiles(stage);
|
|
67
|
+
const manifest = await createOwnershipManifest({
|
|
68
|
+
packageVersion,
|
|
69
|
+
pluginVersion: plugin.version,
|
|
70
|
+
marketplaceName: oldMarketplace?.name ?? 'personal',
|
|
71
|
+
root: stage,
|
|
72
|
+
files,
|
|
73
|
+
});
|
|
74
|
+
await writeJsonAtomic(path.join(stage, '.installer-state.json'), manifest);
|
|
75
|
+
|
|
76
|
+
if (oldState && JSON.stringify(oldState.files) === JSON.stringify(manifest.files) && oldState.packageVersion === manifest.packageVersion) {
|
|
77
|
+
const marketplaceResult = upsertMarketplaceEntry(oldMarketplace, MARKETPLACE_ENTRY);
|
|
78
|
+
if (marketplaceResult.change !== 'unchanged') await writeJsonAtomic(marketplacePath, marketplaceResult.marketplace);
|
|
79
|
+
const marketplaceName = marketplaceResult.marketplace.name;
|
|
80
|
+
registrationMarketplace = marketplaceName;
|
|
81
|
+
const registration = await runCodex(['plugin', 'add', `${PLUGIN_NAME}@${marketplaceName}`]);
|
|
82
|
+
if (registration.code !== 0) throw new Error(registration.stderr || 'Codex plugin registration failed');
|
|
83
|
+
registrationSucceeded = true;
|
|
84
|
+
const verification = await runCodex(['plugin', 'list']);
|
|
85
|
+
if (verification.code !== 0 || !verification.stdout.includes(`${PLUGIN_NAME}@`)) {
|
|
86
|
+
throw new Error(verification.stderr || 'Codex plugin verification failed');
|
|
87
|
+
}
|
|
88
|
+
await rm(stage, { recursive: true, force: true });
|
|
89
|
+
return { status: marketplaceResult.change === 'unchanged' ? 'unchanged' : 'repaired', pluginVersion: plugin.version };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (destinationExists) {
|
|
93
|
+
await rename(destination, backup);
|
|
94
|
+
movedExisting = true;
|
|
95
|
+
}
|
|
96
|
+
await rename(stage, destination);
|
|
97
|
+
installedStage = true;
|
|
98
|
+
|
|
99
|
+
const marketplaceResult = upsertMarketplaceEntry(oldMarketplace, MARKETPLACE_ENTRY);
|
|
100
|
+
await writeJsonAtomic(marketplacePath, marketplaceResult.marketplace);
|
|
101
|
+
const marketplaceName = marketplaceResult.marketplace.name;
|
|
102
|
+
registrationMarketplace = marketplaceName;
|
|
103
|
+
const registration = await runCodex(['plugin', 'add', `${PLUGIN_NAME}@${marketplaceName}`]);
|
|
104
|
+
if (registration.code !== 0) throw new Error(registration.stderr || 'Codex plugin registration failed');
|
|
105
|
+
registrationSucceeded = true;
|
|
106
|
+
const verification = await runCodex(['plugin', 'list']);
|
|
107
|
+
if (verification.code !== 0 || !verification.stdout.includes(`${PLUGIN_NAME}@`)) {
|
|
108
|
+
throw new Error(verification.stderr || 'Codex plugin verification failed');
|
|
109
|
+
}
|
|
110
|
+
await rm(backup, { recursive: true, force: true });
|
|
111
|
+
return { status: destinationExists ? 'updated' : 'installed', pluginVersion: plugin.version };
|
|
112
|
+
} catch (error) {
|
|
113
|
+
await rm(stage, { recursive: true, force: true });
|
|
114
|
+
if (installedStage) await rm(destination, { recursive: true, force: true });
|
|
115
|
+
if (movedExisting && await exists(backup)) await rename(backup, destination);
|
|
116
|
+
if (oldMarketplace === null) await rm(marketplacePath, { force: true });
|
|
117
|
+
else await writeJsonAtomic(marketplacePath, oldMarketplace);
|
|
118
|
+
if (registrationSucceeded) {
|
|
119
|
+
const previousEntry = oldMarketplace?.plugins?.find((plugin) => plugin.name === PLUGIN_NAME);
|
|
120
|
+
const rollback = previousEntry && oldState
|
|
121
|
+
? await runCodex(['plugin', 'add', `${PLUGIN_NAME}@${oldMarketplace.name}`])
|
|
122
|
+
: await runCodex(['plugin', 'remove', `${PLUGIN_NAME}@${registrationMarketplace}`]);
|
|
123
|
+
if (rollback.code !== 0) throw new Error(`${error.message}; Codex rollback failed: ${rollback.stderr || 'unknown error'}`);
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function installPlugin(context) {
|
|
130
|
+
return transact(context, false);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function updatePlugin(context) {
|
|
134
|
+
return transact(context, true);
|
|
135
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { listFiles, sha256File } from './fs-safe.js';
|
|
5
|
+
|
|
6
|
+
export async function readMarketplace(file) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (error.code === 'ENOENT') return null;
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function upsertMarketplaceEntry(input, entry) {
|
|
16
|
+
const marketplace = structuredClone(input ?? {
|
|
17
|
+
name: 'personal', interface: { displayName: 'Personal' }, plugins: [],
|
|
18
|
+
});
|
|
19
|
+
marketplace.plugins ??= [];
|
|
20
|
+
const index = marketplace.plugins.findIndex((plugin) => plugin.name === entry.name);
|
|
21
|
+
if (index === -1) {
|
|
22
|
+
marketplace.plugins.push(structuredClone(entry));
|
|
23
|
+
return { marketplace, change: 'created' };
|
|
24
|
+
}
|
|
25
|
+
const existing = marketplace.plugins[index];
|
|
26
|
+
if (existing.source?.source !== 'local' || existing.source?.path !== entry.source.path) {
|
|
27
|
+
throw new Error(`Marketplace entry ${entry.name} is not owned by this installer`);
|
|
28
|
+
}
|
|
29
|
+
if (JSON.stringify(existing) === JSON.stringify(entry)) return { marketplace, change: 'unchanged' };
|
|
30
|
+
marketplace.plugins[index] = structuredClone(entry);
|
|
31
|
+
return { marketplace, change: 'updated' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function removeMarketplaceEntry(input, pluginName) {
|
|
35
|
+
const marketplace = structuredClone(input);
|
|
36
|
+
const before = marketplace.plugins?.length ?? 0;
|
|
37
|
+
marketplace.plugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
|
|
38
|
+
return { marketplace, removed: marketplace.plugins.length !== before };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function writeJsonAtomic(file, value) {
|
|
42
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
43
|
+
const temporary = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
44
|
+
try {
|
|
45
|
+
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
46
|
+
await rename(temporary, file);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
await rm(temporary, { force: true });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function createOwnershipManifest({ packageVersion, pluginVersion, marketplaceName, root, files }) {
|
|
54
|
+
const checksums = {};
|
|
55
|
+
for (const relative of [...files].sort()) checksums[relative] = await sha256File(path.join(root, relative));
|
|
56
|
+
return {
|
|
57
|
+
schemaVersion: 1,
|
|
58
|
+
packageVersion,
|
|
59
|
+
pluginVersion,
|
|
60
|
+
marketplaceName,
|
|
61
|
+
files: checksums,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function verifyOwnership(manifest, root) {
|
|
66
|
+
const mismatches = [];
|
|
67
|
+
for (const [relative, expected] of Object.entries(manifest.files ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
68
|
+
try {
|
|
69
|
+
if (await sha256File(path.join(root, relative)) !== expected) mismatches.push(relative);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.code === 'ENOENT') mismatches.push(relative);
|
|
72
|
+
else throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const expectedFiles = new Set(Object.keys(manifest.files ?? {}));
|
|
76
|
+
const actualFiles = (await listFiles(root)).filter((relative) => relative !== '.installer-state.json');
|
|
77
|
+
for (const relative of actualFiles) {
|
|
78
|
+
if (!expectedFiles.has(relative)) mismatches.push(relative);
|
|
79
|
+
}
|
|
80
|
+
mismatches.sort();
|
|
81
|
+
return { valid: mismatches.length === 0, mismatches };
|
|
82
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { copyFile, mkdir, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { filesEqual, listFiles } from './fs-safe.js';
|
|
5
|
+
|
|
6
|
+
async function exists(file) {
|
|
7
|
+
try {
|
|
8
|
+
await stat(file);
|
|
9
|
+
return true;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (error.code === 'ENOENT') return false;
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function planProjectInit({ templateRoot, targetRoot, force = false, dryRun = false, clock = () => new Date().toISOString().replaceAll(/[-:.]/g, '') }) {
|
|
17
|
+
const files = await listFiles(templateRoot);
|
|
18
|
+
const actions = [];
|
|
19
|
+
const stamp = clock();
|
|
20
|
+
for (const relative of files) {
|
|
21
|
+
const source = path.join(templateRoot, relative);
|
|
22
|
+
const destination = path.join(targetRoot, relative);
|
|
23
|
+
let type = 'create';
|
|
24
|
+
let backup;
|
|
25
|
+
if (await exists(destination)) {
|
|
26
|
+
if (await filesEqual(source, destination)) type = 'skip';
|
|
27
|
+
else if (force) {
|
|
28
|
+
type = 'replace';
|
|
29
|
+
backup = path.join(targetRoot, '.codex-engineering-skills-backup', stamp, relative);
|
|
30
|
+
} else type = 'conflict';
|
|
31
|
+
}
|
|
32
|
+
actions.push({ type, source, destination, relative, backup, dryRun });
|
|
33
|
+
}
|
|
34
|
+
return actions;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function applyProjectInit(actions) {
|
|
38
|
+
const results = [];
|
|
39
|
+
for (const action of actions) {
|
|
40
|
+
if (action.dryRun || action.type === 'skip' || action.type === 'conflict') {
|
|
41
|
+
results.push({ ...action, applied: false });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
await mkdir(path.dirname(action.destination), { recursive: true });
|
|
45
|
+
if (action.type === 'replace') {
|
|
46
|
+
await mkdir(path.dirname(action.backup), { recursive: true });
|
|
47
|
+
await copyFile(action.destination, action.backup);
|
|
48
|
+
}
|
|
49
|
+
await copyFile(action.source, action.destination);
|
|
50
|
+
results.push({ ...action, applied: true });
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
package/src/uninstall.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFile, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { sha256File } from './fs-safe.js';
|
|
5
|
+
import { readMarketplace, removeMarketplaceEntry, verifyOwnership, writeJsonAtomic } 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 uninstallPlugin({ home, runCodex }) {
|
|
13
|
+
const pluginRoot = path.join(home, 'plugins', 'engineering');
|
|
14
|
+
const statePath = path.join(pluginRoot, '.installer-state.json');
|
|
15
|
+
if (!await exists(statePath)) throw new Error('Engineering plugin is not owned by this installer');
|
|
16
|
+
const state = JSON.parse(await readFile(statePath, 'utf8'));
|
|
17
|
+
const ownership = await verifyOwnership(state, pluginRoot);
|
|
18
|
+
if (!ownership.valid) throw new Error(`Engineering plugin ownership check failed: ${ownership.mismatches.join(', ')}`);
|
|
19
|
+
|
|
20
|
+
const marketplacePath = path.join(home, '.agents', 'plugins', 'marketplace.json');
|
|
21
|
+
const marketplace = await readMarketplace(marketplacePath);
|
|
22
|
+
const entry = marketplace?.plugins?.find((plugin) => plugin.name === 'engineering');
|
|
23
|
+
if (!entry || entry.source?.source !== 'local' || entry.source?.path !== './plugins/engineering') {
|
|
24
|
+
throw new Error('Engineering marketplace entry is not owned by this installer');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const removal = await runCodex(['plugin', 'remove', `engineering@${state.marketplaceName}`]);
|
|
28
|
+
if (removal.code !== 0) throw new Error(removal.stderr || 'Codex plugin removal failed');
|
|
29
|
+
const next = removeMarketplaceEntry(marketplace, 'engineering');
|
|
30
|
+
await writeJsonAtomic(marketplacePath, next.marketplace);
|
|
31
|
+
await rm(pluginRoot, { recursive: true });
|
|
32
|
+
return { status: 'uninstalled', projectArtifactsPreserved: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function purgeProject({ targetRoot, recordedChecksums }) {
|
|
36
|
+
const removed = [];
|
|
37
|
+
const preserved = [];
|
|
38
|
+
for (const [relative, expected] of Object.entries(recordedChecksums).sort(([a], [b]) => a.localeCompare(b))) {
|
|
39
|
+
const file = path.join(targetRoot, relative);
|
|
40
|
+
try {
|
|
41
|
+
if (await sha256File(file) === expected) {
|
|
42
|
+
await rm(file);
|
|
43
|
+
removed.push(relative);
|
|
44
|
+
} else preserved.push(relative);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code !== 'ENOENT') throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { removed, preserved };
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Repository Guidance
|
|
2
|
+
|
|
3
|
+
Keep this file specific to the repository. Replace placeholders with commands and conventions that are true here; remove sections that do not apply.
|
|
4
|
+
|
|
5
|
+
## Repository facts
|
|
6
|
+
|
|
7
|
+
- Setup:
|
|
8
|
+
- Lint:
|
|
9
|
+
- Typecheck:
|
|
10
|
+
- Test:
|
|
11
|
+
- Build:
|
|
12
|
+
- Important directories:
|
|
13
|
+
|
|
14
|
+
## Durable rules
|
|
15
|
+
|
|
16
|
+
- Treat executable code, configuration, tests, migrations, and observed behavior as stronger evidence than stale prose.
|
|
17
|
+
- Preserve unrelated behavior and backward compatibility unless the requirement changes it.
|
|
18
|
+
- Keep external writes, commits, pushes, merges, releases, deployments, and production mutations behind explicit user authorization.
|
|
19
|
+
- Verify changed behavior with the strongest safe repository checks available. Report what ran and what remains unverified.
|
|
20
|
+
|
|
21
|
+
## Task artifacts
|
|
22
|
+
|
|
23
|
+
Use existing files under `docs/agent/` only when they help the current task:
|
|
24
|
+
|
|
25
|
+
- `spec.md`: goal, scope, constraints, acceptance criteria, and unresolved product choices
|
|
26
|
+
- `plan.md`: current execution map, status, dependencies, and verification targets
|
|
27
|
+
- `decision-log.md`: material decisions, evidence, alternatives, and consequences
|
|
28
|
+
- `findings.md`: review findings and their lifecycle
|
|
29
|
+
- `handoff.md`: resumable state when work remains or ownership changes
|
|
30
|
+
|
|
31
|
+
Do not create missing workflow artifacts unless the user requests durable artifacts or runs `npx nono-skills init`. Without them, put the same material information in the final response.
|
|
32
|
+
|
|
33
|
+
Append a decision only when it changes a contract, resolves meaningful ambiguity, accepts risk or a costly tradeoff, changes the plan materially, or records an assumption future work must preserve. Supersede accepted entries instead of rewriting history. Do not log routine edits or shell commands.
|
|
34
|
+
|
|
35
|
+
## Definition of done
|
|
36
|
+
|
|
37
|
+
- The requested outcome and acceptance criteria are satisfied.
|
|
38
|
+
- Relevant targeted checks pass, followed by broader checks in proportion to risk.
|
|
39
|
+
- Compatibility, migration, security, and operational effects are addressed when applicable.
|
|
40
|
+
- Remaining risks, blockers, and unverified assumptions are explicit.
|
|
41
|
+
|
|
42
|
+
## Project conventions
|
|
43
|
+
|
|
44
|
+
Add architecture boundaries, naming rules, generated-file policy, release procedures, ownership, and recurring review guidance here only after they become repository facts.
|
|
45
|
+
|
|
46
|
+
## Skills
|
|
47
|
+
|
|
48
|
+
Let Codex select the smallest applicable set, or invoke one explicitly as `$engineering:<skill>` such as `$engineering:plan` or `$engineering:review`. Skills provide task-specific process; this file provides repository-specific truth.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Decision Log
|
|
2
|
+
|
|
3
|
+
Append material decisions. Never silently rewrite an accepted decision; supersede it with a new entry.
|
|
4
|
+
|
|
5
|
+
## YYYY-MM-DD HH:MM — Decision title
|
|
6
|
+
|
|
7
|
+
- Status: proposed | accepted | superseded | rejected
|
|
8
|
+
- Context:
|
|
9
|
+
- Decision:
|
|
10
|
+
- Alternatives:
|
|
11
|
+
- Evidence and rationale:
|
|
12
|
+
- Consequences:
|
|
13
|
+
- Follow-up:
|
|
14
|
+
- Related:
|
|
15
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Findings
|
|
2
|
+
|
|
3
|
+
## Finding ID — Short title
|
|
4
|
+
|
|
5
|
+
- Severity: critical | high | medium | low
|
|
6
|
+
- State: open | accepted | fixed | verified | wont-fix | not-reproducible
|
|
7
|
+
- Evidence:
|
|
8
|
+
- Impact:
|
|
9
|
+
- Reproduction or reasoning:
|
|
10
|
+
- Remediation direction:
|
|
11
|
+
- Verification:
|
|
12
|
+
- Owner or follow-up:
|
|
13
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Working Plan
|
|
2
|
+
|
|
3
|
+
Status values: `pending`, `in-progress`, `blocked`, `done`.
|
|
4
|
+
|
|
5
|
+
| Status | Work item | Verification | Notes |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| pending | | | |
|
|
8
|
+
|
|
9
|
+
## Risks and dependencies
|
|
10
|
+
|
|
11
|
+
## Human decisions needed
|
|
12
|
+
|
|
13
|
+
## Latest checkpoint
|
|
14
|
+
|