@justethales/cockpit 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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/check.js +228 -0
- package/dist/cli.js +72 -0
- package/dist/init.js +78 -0
- package/dist/new.js +98 -0
- package/dist/shared.js +63 -0
- package/dist/status.js +127 -0
- package/package.json +55 -0
- package/skills/cockpit/SKILL.md +70 -0
- package/skills/next/SKILL.md +139 -0
- package/templates/README.md +107 -0
- package/templates/now.md +55 -0
- package/templates/roadmap.md +64 -0
- package/templates/state.json +14 -0
- package/templates/templates/audit-brief.md +107 -0
- package/templates/templates/session-log.md +86 -0
- package/templates/templates/session-prompt.md +121 -0
package/dist/new.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit new prompt|log --slug <kebab-id>` — copy a template, interpolate.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, relative } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { exit } from 'node:process';
|
|
8
|
+
import { c, todayISO } from './shared.js';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
// Sub-templates (session-prompt, session-log, audit-brief) live at
|
|
12
|
+
// templates/templates/ — the same path they end up at inside the user's
|
|
13
|
+
// scaffolded cockpit/. Init copies the whole tree ; `new` reads from the
|
|
14
|
+
// nested sub-dir only.
|
|
15
|
+
const TEMPLATES = join(__dirname, '..', 'templates', 'templates');
|
|
16
|
+
function nextLogIndex(dir) {
|
|
17
|
+
if (!existsSync(dir))
|
|
18
|
+
return '001';
|
|
19
|
+
const today = todayISO();
|
|
20
|
+
const yymmdd = today.slice(2).replace(/-/g, '-'); // YY-MM-DD
|
|
21
|
+
const todayLogs = readdirSync(dir).filter((f) => f.startsWith(yymmdd) && f.endsWith('.md'));
|
|
22
|
+
const maxNNN = todayLogs.reduce((max, f) => {
|
|
23
|
+
const m = f.match(/^\d{2}-\d{2}-\d{2}-(\d{3})/);
|
|
24
|
+
if (!m)
|
|
25
|
+
return max;
|
|
26
|
+
return Math.max(max, parseInt(m[1], 10));
|
|
27
|
+
}, 0);
|
|
28
|
+
return String(maxNNN + 1).padStart(3, '0');
|
|
29
|
+
}
|
|
30
|
+
function getArg(args, flag) {
|
|
31
|
+
const i = args.indexOf(flag);
|
|
32
|
+
if (i === -1)
|
|
33
|
+
return undefined;
|
|
34
|
+
return args[i + 1];
|
|
35
|
+
}
|
|
36
|
+
export function runNew(args) {
|
|
37
|
+
const [kind, ...rest] = args;
|
|
38
|
+
if (kind !== 'prompt' && kind !== 'log') {
|
|
39
|
+
console.error(c.red(`unknown new kind: ${kind}`));
|
|
40
|
+
console.error(c.gray(' → use `cockpit new prompt --slug X` or `cockpit new log --slug X`'));
|
|
41
|
+
exit(1);
|
|
42
|
+
}
|
|
43
|
+
const slug = getArg(rest, '--slug');
|
|
44
|
+
if (!slug) {
|
|
45
|
+
console.error(c.red('--slug <kebab-id> is required'));
|
|
46
|
+
exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (!/^[a-z0-9-]+$/.test(slug)) {
|
|
49
|
+
console.error(c.red(`slug must be kebab-case (a-z, 0-9, -): got '${slug}'`));
|
|
50
|
+
exit(1);
|
|
51
|
+
}
|
|
52
|
+
const root = process.cwd();
|
|
53
|
+
const today = todayISO();
|
|
54
|
+
if (kind === 'prompt') {
|
|
55
|
+
const dir = join(root, 'docs', 'plan', 'sessions');
|
|
56
|
+
mkdirSync(dir, { recursive: true });
|
|
57
|
+
const filename = `${slug.toUpperCase().replace(/-/g, '-')}.md`;
|
|
58
|
+
const dest = join(dir, filename);
|
|
59
|
+
if (existsSync(dest)) {
|
|
60
|
+
console.error(c.red(`already exists: ${relative(root, dest)}`));
|
|
61
|
+
exit(1);
|
|
62
|
+
}
|
|
63
|
+
const src = join(TEMPLATES, 'session-prompt.md');
|
|
64
|
+
const raw = readFileSync(src, 'utf8');
|
|
65
|
+
const out = raw
|
|
66
|
+
.split('YYYY-MM-DD').join(today)
|
|
67
|
+
.split('<id>-<slug>').join(slug);
|
|
68
|
+
writeFileSync(dest, out);
|
|
69
|
+
console.log(`${c.green('write')} ${relative(root, dest)}`);
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log(c.gray('next: edit the prompt to fill the <placeholders>'));
|
|
72
|
+
console.log(c.gray(' then update cockpit/state.json next_prompt to point at this file'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (kind === 'log') {
|
|
76
|
+
const dir = join(root, 'session-logs');
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
const yymmdd = today.slice(2);
|
|
79
|
+
const nnn = nextLogIndex(dir);
|
|
80
|
+
const filename = `${yymmdd}-${nnn}-${slug}.md`;
|
|
81
|
+
const dest = join(dir, filename);
|
|
82
|
+
if (existsSync(dest)) {
|
|
83
|
+
console.error(c.red(`already exists: ${relative(root, dest)}`));
|
|
84
|
+
exit(1);
|
|
85
|
+
}
|
|
86
|
+
const src = join(TEMPLATES, 'session-log.md');
|
|
87
|
+
const raw = readFileSync(src, 'utf8');
|
|
88
|
+
const out = raw
|
|
89
|
+
.split('YY-MM-DD-NNN').join(`${yymmdd}-${nnn}`)
|
|
90
|
+
.split('YY-MM-DD').join(yymmdd);
|
|
91
|
+
writeFileSync(dest, out);
|
|
92
|
+
console.log(`${c.green('write')} ${relative(root, dest)}`);
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log(c.gray(`session id: ${yymmdd}-${nnn}-${slug}`));
|
|
95
|
+
console.log(c.gray('next: edit the log, then update cockpit/state.json last_session_id'));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers — ANSI colors, git, frontmatter parsing.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
6
|
+
import { stdout } from 'node:process';
|
|
7
|
+
import { parse as parseYaml } from 'yaml';
|
|
8
|
+
export const COLOR_ON = stdout.isTTY && !process.env.NO_COLOR;
|
|
9
|
+
export const c = {
|
|
10
|
+
red: (s) => (COLOR_ON ? `\x1b[31m${s}\x1b[0m` : s),
|
|
11
|
+
green: (s) => (COLOR_ON ? `\x1b[32m${s}\x1b[0m` : s),
|
|
12
|
+
yellow: (s) => (COLOR_ON ? `\x1b[33m${s}\x1b[0m` : s),
|
|
13
|
+
cyan: (s) => (COLOR_ON ? `\x1b[36m${s}\x1b[0m` : s),
|
|
14
|
+
gray: (s) => (COLOR_ON ? `\x1b[90m${s}\x1b[0m` : s),
|
|
15
|
+
bold: (s) => (COLOR_ON ? `\x1b[1m${s}\x1b[0m` : s)
|
|
16
|
+
};
|
|
17
|
+
export function setColor(on) {
|
|
18
|
+
// Used by --plain to disable colors mid-run.
|
|
19
|
+
Object.keys(c).forEach((k) => {
|
|
20
|
+
if (!on)
|
|
21
|
+
c[k] = (s) => s;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function git(cmd, cwd = process.cwd()) {
|
|
25
|
+
try {
|
|
26
|
+
return execSync(`git ${cmd}`, {
|
|
27
|
+
cwd,
|
|
28
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
29
|
+
})
|
|
30
|
+
.toString()
|
|
31
|
+
.trim();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function readFrontmatter(filePath) {
|
|
38
|
+
if (!existsSync(filePath))
|
|
39
|
+
return null;
|
|
40
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
41
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
42
|
+
if (!m)
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
return parseYaml(m[1]);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function loadState(path) {
|
|
52
|
+
if (!existsSync(path))
|
|
53
|
+
return null;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function todayISO() {
|
|
62
|
+
return new Date().toISOString().slice(0, 10);
|
|
63
|
+
}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit status` — read-only one-screen snapshot.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { c, git, loadState, readFrontmatter, setColor } from './shared.js';
|
|
7
|
+
const ROOT = process.cwd();
|
|
8
|
+
const STATE = join(ROOT, 'cockpit', 'state.json');
|
|
9
|
+
const NOW = join(ROOT, 'cockpit', 'now.md');
|
|
10
|
+
const ROADMAP = join(ROOT, 'cockpit', 'roadmap.md');
|
|
11
|
+
function section(label, body) {
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(c.bold(label));
|
|
14
|
+
console.log(c.gray('─'.repeat(Math.max(label.length, 40))));
|
|
15
|
+
console.log(body);
|
|
16
|
+
}
|
|
17
|
+
export function runStatus(args) {
|
|
18
|
+
if (args.includes('--plain'))
|
|
19
|
+
setColor(false);
|
|
20
|
+
if (!existsSync(STATE)) {
|
|
21
|
+
console.error(c.red('no cockpit/state.json found'));
|
|
22
|
+
console.error(c.gray(' → run `npx cockpit init` first'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const state = loadState(STATE);
|
|
26
|
+
if (!state) {
|
|
27
|
+
console.error(c.red('cockpit/state.json is not valid JSON'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const head = git('rev-parse --short HEAD') || '(no git)';
|
|
31
|
+
const branch = git('rev-parse --abbrev-ref HEAD') || '(no git)';
|
|
32
|
+
const dirty = git('status --short');
|
|
33
|
+
const ahead = git('rev-list --count @{u}..HEAD').trim();
|
|
34
|
+
const log10 = git('log --oneline -10');
|
|
35
|
+
let pkgName = 'cockpit-managed-project';
|
|
36
|
+
let pkgVersion = '';
|
|
37
|
+
const pkgPath = join(ROOT, 'package.json');
|
|
38
|
+
if (existsSync(pkgPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
41
|
+
pkgName = pkg.name ?? pkgName;
|
|
42
|
+
pkgVersion = pkg.version ? `@${pkg.version}` : '';
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* ignore */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(c.bold(`${pkgName}${pkgVersion}`) +
|
|
50
|
+
` · branch ${c.cyan(branch)} · HEAD ${c.cyan(head)}`);
|
|
51
|
+
if (dirty) {
|
|
52
|
+
const lines = dirty.split('\n').length;
|
|
53
|
+
console.log(c.yellow(` ⚠ working tree has ${lines} uncommitted file${lines > 1 ? 's' : ''}`));
|
|
54
|
+
}
|
|
55
|
+
if (ahead && ahead !== '0') {
|
|
56
|
+
console.log(c.yellow(` ⚠ ${ahead} commit${ahead === '1' ? '' : 's'} ahead of upstream`));
|
|
57
|
+
}
|
|
58
|
+
const summary = [
|
|
59
|
+
` current_phase ${c.cyan(String(state.current_phase ?? '-'))}`,
|
|
60
|
+
` next_phase ${c.cyan(String(state.next_phase ?? '-'))}`,
|
|
61
|
+
` next_prompt ${c.cyan(String(state.next_prompt ?? '-'))}`,
|
|
62
|
+
` last_session_id ${c.gray(String(state.last_session_id ?? '-'))}`,
|
|
63
|
+
` last_commit ${c.gray(String(state.last_commit ?? '-'))}`
|
|
64
|
+
].join('\n');
|
|
65
|
+
section('STATE', summary);
|
|
66
|
+
const nextPromptPath = state.next_prompt ? join(ROOT, String(state.next_prompt)) : null;
|
|
67
|
+
if (nextPromptPath && existsSync(nextPromptPath)) {
|
|
68
|
+
const fm = readFrontmatter(nextPromptPath);
|
|
69
|
+
const status = fm ? String(fm.status ?? '?') : '(no frontmatter)';
|
|
70
|
+
const sessionLog = fm ? String(fm.session_log ?? 'pending') : '?';
|
|
71
|
+
const statusColor = status === 'shipped'
|
|
72
|
+
? c.red(status)
|
|
73
|
+
: status === 'queued'
|
|
74
|
+
? c.green(status)
|
|
75
|
+
: c.yellow(status);
|
|
76
|
+
const body = [
|
|
77
|
+
` path ${c.cyan(String(state.next_prompt))}`,
|
|
78
|
+
` status ${statusColor}`,
|
|
79
|
+
` log ${c.gray(sessionLog)}`
|
|
80
|
+
].join('\n');
|
|
81
|
+
section('NEXT PROMPT', body);
|
|
82
|
+
const raw = readFileSync(nextPromptPath, 'utf8');
|
|
83
|
+
const afterFm = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
84
|
+
const lines = afterFm.split('\n').slice(0, 8);
|
|
85
|
+
for (const l of lines) {
|
|
86
|
+
if (!l.trim())
|
|
87
|
+
continue;
|
|
88
|
+
console.log(c.gray(' ' + l.slice(0, 100)));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (state.next_prompt) {
|
|
92
|
+
section('NEXT PROMPT', c.red(` ✗ ${state.next_prompt} does not exist on disk`));
|
|
93
|
+
}
|
|
94
|
+
if (log10) {
|
|
95
|
+
section('RECENT COMMITS', log10
|
|
96
|
+
.split('\n')
|
|
97
|
+
.map((l) => ` ${l}`)
|
|
98
|
+
.join('\n'));
|
|
99
|
+
}
|
|
100
|
+
if (existsSync(NOW)) {
|
|
101
|
+
const raw = readFileSync(NOW, 'utf8');
|
|
102
|
+
const m = raw.match(/## Current focus[^\n]*\n\n([\s\S]*?)(?:\n---|\n##)/);
|
|
103
|
+
if (m) {
|
|
104
|
+
const focus = m[1].trim().split('\n').slice(0, 4).join('\n');
|
|
105
|
+
section('CURRENT FOCUS', focus
|
|
106
|
+
.split('\n')
|
|
107
|
+
.map((l) => ` ${l}`)
|
|
108
|
+
.join('\n'));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (existsSync(ROADMAP)) {
|
|
112
|
+
const raw = readFileSync(ROADMAP, 'utf8');
|
|
113
|
+
const m = raw.match(/## Now — Next 3[^\n]*\n([\s\S]*?)(?:\n---|\n## )/);
|
|
114
|
+
if (m) {
|
|
115
|
+
const block = m[1]
|
|
116
|
+
.split('\n')
|
|
117
|
+
.filter((l) => l.trim().length > 0)
|
|
118
|
+
.slice(0, 8)
|
|
119
|
+
.map((l) => ' ' + l.slice(0, 140))
|
|
120
|
+
.join('\n');
|
|
121
|
+
section('NEXT 3', block);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(c.gray('run `npx cockpit check` to validate, `npx cockpit status` to refresh'));
|
|
126
|
+
console.log('');
|
|
127
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@justethales/cockpit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A 200-line discipline for AI-coding sessions: machine-verifiable session state + drift validator + canonical templates. Works with Claude Code, Cursor, or anything that drives a repo with a session loop.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Thales (Juste Gnimavo) — ZeroSuite",
|
|
8
|
+
"homepage": "https://github.com/justethales/cockpit-skill",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/justethales/cockpit-skill.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/justethales/cockpit-skill/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"cursor",
|
|
21
|
+
"workflow",
|
|
22
|
+
"developer-experience",
|
|
23
|
+
"session",
|
|
24
|
+
"cockpit"
|
|
25
|
+
],
|
|
26
|
+
"main": "dist/cli.js",
|
|
27
|
+
"bin": {
|
|
28
|
+
"cockpit": "dist/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"cockpit": "tsx src/cli.ts",
|
|
32
|
+
"build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
33
|
+
"smoke": "tsx src/cli.ts --version",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"yaml": "^2.7.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.13.4",
|
|
41
|
+
"tsx": "^4.19.2",
|
|
42
|
+
"typescript": "^5.7.3"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"templates",
|
|
47
|
+
"skills",
|
|
48
|
+
"README.md",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"CHANGELOG.md"
|
|
51
|
+
],
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=20"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cockpit
|
|
3
|
+
description: |
|
|
4
|
+
Quick KPI lookups for any cockpit-managed project. Answers "where are we?",
|
|
5
|
+
"what's next?", "did the last session close cleanly?". Reads the project's
|
|
6
|
+
cockpit/ directory if present, falls back to git + package.json + README.
|
|
7
|
+
Subcommands: where, roadmap, changelog, version, ship, stack, status, check, or
|
|
8
|
+
no args = full snapshot.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# /cockpit — KPI lookups
|
|
12
|
+
|
|
13
|
+
You are a read-only status reporter. The user invoked `/cockpit` to get the "where am I" / "what's next" snapshot WITHOUT making you re-read the codebase. Do NOT explore beyond what the subcommand asks for.
|
|
14
|
+
|
|
15
|
+
## Pre-flight (run once, in parallel)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pwd
|
|
19
|
+
test -d cockpit && echo "cockpit:yes" || echo "cockpit:no"
|
|
20
|
+
git rev-parse --short HEAD 2>/dev/null
|
|
21
|
+
git rev-parse --abbrev-ref HEAD 2>/dev/null
|
|
22
|
+
git status --short
|
|
23
|
+
git log --oneline -10
|
|
24
|
+
test -f package.json && jq -r '.name + "@" + (.version // "unversioned")' package.json 2>/dev/null
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Path A — `cockpit/` directory present
|
|
28
|
+
|
|
29
|
+
Prefer the CLI : `npx cockpit status` (or `pnpm cockpit:status` / `bun cockpit:status` if the project has wired it). The CLI output is the canonical snapshot — surface it verbatim, don't paraphrase.
|
|
30
|
+
|
|
31
|
+
If the CLI fails or isn't installed, fall back to reading the cockpit files directly :
|
|
32
|
+
|
|
33
|
+
- `cat cockpit/state.json | jq` — machine state.
|
|
34
|
+
- `cockpit/now.md` — current focus, next-actions-by-budget, don't-get-distracted.
|
|
35
|
+
- `cockpit/roadmap.md` Next-3 — what ships next.
|
|
36
|
+
|
|
37
|
+
## Path B — no cockpit/ directory
|
|
38
|
+
|
|
39
|
+
Degraded mode :
|
|
40
|
+
- `git log --oneline -10` — recent activity.
|
|
41
|
+
- `cat README.md | head -30` — what the project is.
|
|
42
|
+
- Suggest `npx cockpit init` to scaffold.
|
|
43
|
+
|
|
44
|
+
## Subcommands
|
|
45
|
+
|
|
46
|
+
| Invocation | Returns |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `/cockpit` | Full snapshot (state + next prompt preview + last 10 commits + current focus + Next-3). Same as `npx cockpit status`. |
|
|
49
|
+
| `/cockpit where` | `cockpit/now.md` — current focus + 15-min next + don't-do list. |
|
|
50
|
+
| `/cockpit roadmap` | `cockpit/roadmap.md` Next-3 + in-flight + blocked. |
|
|
51
|
+
| `/cockpit changelog` | `git log --oneline -20` + `git log --since='7 days ago' --oneline` count. |
|
|
52
|
+
| `/cockpit version` | `pkg@version` + commit SHA + branch + unpushed count + dirty count + state.json migration count + current_phase. |
|
|
53
|
+
| `/cockpit ship` | Ship-readiness card : working tree, unpushed, blockers (from roadmap), in-flight, verdict line ("safe to push" / "FAIL : <reason>"). |
|
|
54
|
+
| `/cockpit stack` | `package.json` runtime deps + scripts + 1-line architecture from `cockpit/architecture.md` if present. |
|
|
55
|
+
| `/cockpit status` | Synonym for `/cockpit` (no args). |
|
|
56
|
+
| `/cockpit check` | Run `npx cockpit check` and surface the result. |
|
|
57
|
+
|
|
58
|
+
## Rules
|
|
59
|
+
|
|
60
|
+
- **Read-only.** This skill never edits files. If the user wants to edit, suggest the appropriate tool.
|
|
61
|
+
- **Surface verbatim.** Don't paraphrase the CLI output. The CLI is the source of truth.
|
|
62
|
+
- **No explore.** Don't grep, don't search the codebase. The cockpit gives the answer ; trust it.
|
|
63
|
+
- **No re-run.** If the user just ran `/cockpit` 30 seconds ago, the state hasn't meaningfully changed. Don't burn tokens.
|
|
64
|
+
- **Honor `pwd`.** Never reach for a sibling project's cockpit.
|
|
65
|
+
|
|
66
|
+
## What `/cockpit` is NOT
|
|
67
|
+
|
|
68
|
+
- Not `/next` — cockpit reports, next acts.
|
|
69
|
+
- Not an editor — never bumps state.json or rewrites now.md.
|
|
70
|
+
- Not a planner — surfaces the existing plan, doesn't propose new ones.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: next
|
|
3
|
+
description: |
|
|
4
|
+
Start the next implementation session. Reads cockpit/state.json for the
|
|
5
|
+
next_prompt field, opens the named prompt file, then begins executing it
|
|
6
|
+
as the canonical instruction for the session. Works in any project with a
|
|
7
|
+
cockpit/ directory. Falls back to roadmap.md Next-3 when no next_prompt is
|
|
8
|
+
set, and to git log + open questions when no cockpit exists at all. Removes
|
|
9
|
+
the friction of having to paste the previous session's end message or
|
|
10
|
+
re-orient the agent on session start.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# /next — Start the next implementation session
|
|
14
|
+
|
|
15
|
+
You are an **execution agent**, not a reporter. The user typed `/next` because they want work to begin. Your job is to (1) discover what the next session is, (2) load its full context, (3) start executing it. Do NOT stop and ask "shall I proceed" — proceed.
|
|
16
|
+
|
|
17
|
+
This is the opposite of `/cockpit` in posture : cockpit reports, next acts.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Pre-flight (run once, in parallel)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pwd
|
|
25
|
+
test -d cockpit && echo "cockpit:yes" || echo "cockpit:no"
|
|
26
|
+
git rev-parse --short HEAD 2>/dev/null
|
|
27
|
+
git rev-parse --abbrev-ref HEAD 2>/dev/null
|
|
28
|
+
git status --short
|
|
29
|
+
git log --oneline -5
|
|
30
|
+
test -f package.json && jq -r '.name + "@" + (.version // "unversioned")' package.json 2>/dev/null
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Surface the project name + commit + branch at the top of your reply (one line).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Validate the cockpit first
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx cockpit check --quiet 2>&1 | tail -5
|
|
41
|
+
echo "exit:$?"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If `cockpit check` returns FAIL : **STOP.** The previous session didn't close cleanly. Surface the FAIL findings to the user and ask whether to (a) fix the drift inline first, (b) re-execute the prompt the cockpit points at, (c) start something else. Don't paper over drift by executing on top of it — that compounds the problem.
|
|
45
|
+
|
|
46
|
+
If 0 FAIL : proceed.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Decide what to execute
|
|
51
|
+
|
|
52
|
+
### Path A — cockpit present and `next_prompt` set (the happy path)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
test -f cockpit/state.json && jq -r '.next_prompt // "none"' cockpit/state.json
|
|
56
|
+
test -f cockpit/state.json && jq -r '.current_phase, .next_phase' cockpit/state.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
When `next_prompt` is a real path (not `"none"`, not empty, not null) :
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cat <next_prompt path>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The file is the canonical instruction for this session. Read it fully.
|
|
66
|
+
|
|
67
|
+
**Validate before executing :**
|
|
68
|
+
- Frontmatter `status:` should be `queued` (not `shipped`). The `cockpit check` ran above catches this, but verify by eye on the prompt.
|
|
69
|
+
- Frontmatter `next_after:` should reference the most recent shipped work.
|
|
70
|
+
- The prompt's `## CONTEXT` section should mention the most recent commit hash.
|
|
71
|
+
|
|
72
|
+
**When validation passes :** announce in one sentence what you're about to do. Then **begin the work** — Build step 1, then 2, then 3. Do NOT pause to re-confirm scope ; the prompt IS the scope.
|
|
73
|
+
|
|
74
|
+
### Path B — cockpit present but `next_prompt` is `none` / missing
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
test -f cockpit/roadmap.md && sed -n '/## Now — Next 3/,/^---$/p' cockpit/roadmap.md
|
|
78
|
+
ls docs/plan/sessions/*.md 2>/dev/null | head -20
|
|
79
|
+
grep -l '^status: queued' docs/plan/sessions/*.md 2>/dev/null
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
When the roadmap Next-1 names a prompt file that exists and has `status: queued`, treat that as `next_prompt` and continue with Path A.
|
|
83
|
+
|
|
84
|
+
When no clear next-prompt can be derived, surface the Next-3 from `roadmap.md` and ask the user which to start. Don't guess.
|
|
85
|
+
|
|
86
|
+
### Path C — no cockpit at all
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
test -f README.md && head -20 README.md
|
|
90
|
+
test -d docs/plan/sessions && ls docs/plan/sessions/*.md 2>/dev/null
|
|
91
|
+
git log --oneline -10
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Report what you see + suggest `npx cockpit init`. The skill is degraded but useful — at least it does the inspection legwork.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## During execution
|
|
99
|
+
|
|
100
|
+
- **Honor the prompt's `## DO NOT` section literally.** Scope creep guard.
|
|
101
|
+
- **Honor the prompt's `## AT END OF SESSION` section literally.** In particular, the steps about session log + cockpit update + next-session prompt drafting + `cockpit check` + commit + push are non-negotiable.
|
|
102
|
+
- **If the prompt's MUST list is larger than the session can realistically cover, surface the call out loud before starting.** Quote the prompt back and propose the cut.
|
|
103
|
+
- **If the prompt assumes a file / env var / external state that doesn't exist, stop immediately.** Surface the missing precondition.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## When the prompt finishes
|
|
108
|
+
|
|
109
|
+
The prompt's `## AT END OF SESSION` already covers commit + push + cockpit bump + next-session prompt drafting + `cockpit check`. Don't add extra ceremony.
|
|
110
|
+
|
|
111
|
+
If the prompt is silent on the next-session prompt draft, draft it anyway via `npx cockpit new prompt --slug <next-slug>`.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Rules
|
|
116
|
+
|
|
117
|
+
- **Never paste a long copy of `cockpit/now.md` or the prompt body into your reply.** The user reads files themselves ; your job is to act.
|
|
118
|
+
- **Never report "I've read the cockpit, what would you like me to do?"** That's the friction this skill removes. If the cockpit names a next-prompt, execute it.
|
|
119
|
+
- **Never re-run `/cockpit` from inside `/next`.** Duplicates work.
|
|
120
|
+
- **Always honor `pwd`.** Never reach for a sibling project's cockpit.
|
|
121
|
+
- **Never modify `cockpit/state.json` until the session's work is shipping.** The skill is a starting gun, not a state-bump tool.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Failure modes
|
|
126
|
+
|
|
127
|
+
- **`pwd` is not in a git repo** : print "_not in a git repository ; `/next` needs a project root_" and stop.
|
|
128
|
+
- **No `cockpit/`, no `docs/plan/sessions/`, no obvious next work** : print "_no cockpit, no session prompts ; tell me what to start_" and suggest `npx cockpit init`.
|
|
129
|
+
- **`next_prompt` points at a missing file** : the cockpit-check above catches this. Surface + ask whether to draft the missing prompt or fix state.json.
|
|
130
|
+
- **`next_prompt` points at a `shipped` prompt** : cockpit-check catches this. Surface + ask whether to update state.json to the real next slice OR re-execute the shipped prompt.
|
|
131
|
+
- **The prompt's MUST is obviously infeasible in one session** : propose the cut to the user before starting.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## What `/next` is NOT
|
|
136
|
+
|
|
137
|
+
- It is **not** `/cockpit`. Cockpit reports, next acts.
|
|
138
|
+
- It is **not** a planner. The prompt file IS the plan.
|
|
139
|
+
- It is **not** interactive. It begins work ; the user interrupts if redirection is needed.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Cockpit
|
|
2
|
+
|
|
3
|
+
> **Scaffolded** : {{TODAY}} via `npx cockpit init`.
|
|
4
|
+
> **Source** : `@thales/cockpit` — https://github.com/justethales/cockpit-skill
|
|
5
|
+
> **License** : MIT.
|
|
6
|
+
|
|
7
|
+
This directory is the **single source of short-term truth** for this project's AI-driven engineering sessions. It costs ~3-5 k tokens to load, far less than re-reading the codebase.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What this is
|
|
12
|
+
|
|
13
|
+
Six state files + three CLI tools + three canonical templates + an explicit session-start / session-end protocol so any fresh agent session can answer "where am I?" in one screen, leave the cockpit in a coherent state on close, and have a machine-verifiable gate to catch drift before push.
|
|
14
|
+
|
|
15
|
+
### State files
|
|
16
|
+
|
|
17
|
+
- [now.md](now.md) — current focus (1 sentence), next action by time budget, don't-get-distracted list, active constraints. **Update at every session close.**
|
|
18
|
+
- [roadmap.md](roadmap.md) — Next 3 to ship, in-flight, blocked, queued, shipped-this-week, phase scoreboard.
|
|
19
|
+
- [state.json](state.json) — machine-readable single-line state. The validator (`npx cockpit check`) reads it.
|
|
20
|
+
- [templates/](templates/) — canonical scaffolds for the three artifacts every session produces.
|
|
21
|
+
|
|
22
|
+
### CLI tools
|
|
23
|
+
|
|
24
|
+
| Command | What it does | When to run |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `npx cockpit status` | Read-only one-screen snapshot. | Session start. |
|
|
27
|
+
| `npx cockpit check` | Validator. Exits 1 on drift. | **Mandatory before `git push`** when the cockpit was bumped. |
|
|
28
|
+
| `npx cockpit new prompt --slug X` | Scaffold a session prompt from template. | Session close (draft next session's prompt). |
|
|
29
|
+
| `npx cockpit new log --slug X` | Scaffold a session log from template. | Session close. |
|
|
30
|
+
|
|
31
|
+
### Templates
|
|
32
|
+
|
|
33
|
+
| Path | Use case |
|
|
34
|
+
|---|---|
|
|
35
|
+
| [templates/session-prompt.md](templates/session-prompt.md) | The skeleton for `docs/plan/sessions/<id>-<slug>.md`. |
|
|
36
|
+
| [templates/session-log.md](templates/session-log.md) | The skeleton for `session-logs/YY-MM-DD-NNN-<slug>.md`. |
|
|
37
|
+
| [templates/audit-brief.md](templates/audit-brief.md) | The Explore sub-agent brief for post-implementation audits. |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Session-start protocol
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx cockpit status
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
That covers the snapshot. Then `cat <next_prompt path>` and begin executing. Don't pause to confirm scope ; the prompt IS the scope.
|
|
48
|
+
|
|
49
|
+
If `npx cockpit check` returns FAIL at session start, **STOP and reconcile.** A failed cockpit means the previous session didn't close cleanly.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Session-close protocol
|
|
54
|
+
|
|
55
|
+
In order. The order matters because the validator depends on prior steps :
|
|
56
|
+
|
|
57
|
+
1. **Write the session log** — `npx cockpit new log --slug X` then fill in.
|
|
58
|
+
2. **Flip this session's prompt frontmatter** — `status: queued → shipped`, `session_id:` filled, `session_log:` pointing at step 1's file.
|
|
59
|
+
3. **Bump parent prompt's `progress:` block** if this was a sub-slice.
|
|
60
|
+
4. **Draft the next session's prompt** — `npx cockpit new prompt --slug Y` then fill in.
|
|
61
|
+
5. **`cockpit/now.md`** — overwrite the three blocks. Update the "Updated" line.
|
|
62
|
+
6. **`cockpit/roadmap.md`** — move shipped item to "Shipped this week" ; promote queued ; update Phase scoreboard.
|
|
63
|
+
7. **`cockpit/state.json`** — bump `last_session_id`, `last_commit`, `current_phase`, `next_phase`, `next_prompt`, append to `phases_shipped[]`, append to `migrations_applied[]` if a migration ran.
|
|
64
|
+
8. **`npx cockpit check`** — must exit 0 (0 FAIL).
|
|
65
|
+
9. **`git add` + `git commit` + `git push`**.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## What the validator catches
|
|
70
|
+
|
|
71
|
+
Real drift, in order of severity :
|
|
72
|
+
|
|
73
|
+
1. **`state.json.next_prompt` points at a missing file.**
|
|
74
|
+
2. **`state.json.next_prompt` points at a `shipped` prompt.** Cockpit was never bumped after the previous session.
|
|
75
|
+
3. **`state.json.last_session_id` does not map to a session-log file.**
|
|
76
|
+
4. **`state.json.last_commit` not found in `git log`.**
|
|
77
|
+
5. **`state.json.phases_shipped[]` has duplicates.**
|
|
78
|
+
6. **`state.json.migrations_applied[]` does not match `<migrations_dir>/*.sql`.**
|
|
79
|
+
7. **A session prompt has `status: shipped` but no `session_log:` pointer.**
|
|
80
|
+
8. **Uncommitted changes in `cockpit/` / `docs/plan/sessions/` / `session-logs/`.**
|
|
81
|
+
|
|
82
|
+
Each failure prints a `→ fix` hint so the next session can resolve without re-reading this README.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## When NOT to use the cockpit
|
|
87
|
+
|
|
88
|
+
- Trivial fix (typo, dead import, one-line bug). Just do it.
|
|
89
|
+
- A question scoped to a file you already know.
|
|
90
|
+
- Executing a prompt that's explicitly in Next-3 and `cockpit status` shows no drift.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Failure modes the cockpit cannot fix
|
|
95
|
+
|
|
96
|
+
- A session that lies in `now.md` about what was built. The validator reads metadata, not intent.
|
|
97
|
+
- A prompt frontmatter that says `status: queued` but the body describes shipped work.
|
|
98
|
+
|
|
99
|
+
For each : prevention beats detection. Templates + protocol + validator are the prevention layer. Honest description + human review are the last line.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Learn more
|
|
104
|
+
|
|
105
|
+
- Source : https://github.com/justethales/cockpit-skill
|
|
106
|
+
- Blog post : https://thalesandhisaictoclaude.com
|
|
107
|
+
- Issues / feedback : https://github.com/justethales/cockpit-skill/issues
|