@sabaiway/agent-workflow-kit 1.0.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 +32 -0
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/SKILL.md +121 -0
- package/bin/install.mjs +139 -0
- package/launchers/README.md +33 -0
- package/launchers/install-launchers.sh +94 -0
- package/launchers/windsurf-workflow.md +30 -0
- package/migrations/README.md +41 -0
- package/package.json +46 -0
- package/references/planning.md +105 -0
- package/references/scripts/_expect-shim.mjs +41 -0
- package/references/scripts/archive-changelog.mjs +441 -0
- package/references/scripts/archive-changelog.test.mjs +212 -0
- package/references/scripts/archive-issues.mjs +179 -0
- package/references/scripts/archive-issues.test.mjs +95 -0
- package/references/scripts/check-docs-size.mjs +353 -0
- package/references/scripts/check-docs-size.test.mjs +180 -0
- package/references/scripts/install-git-hooks.mjs +83 -0
- package/references/templates/AGENTS.md +78 -0
- package/references/templates/active_plan.md +31 -0
- package/references/templates/agent_rules.md +85 -0
- package/references/templates/architecture.md +49 -0
- package/references/templates/changelog.md +24 -0
- package/references/templates/current_state.md +36 -0
- package/references/templates/decisions.md +44 -0
- package/references/templates/env_commands.md +41 -0
- package/references/templates/handover.md +37 -0
- package/references/templates/known_issues.md +33 -0
- package/references/templates/pages/PAGE_TEMPLATE.md +53 -0
- package/references/templates/pages/index.md +23 -0
- package/references/templates/pages/shared-patterns.md +30 -0
- package/references/templates/tech_reference.md +34 -0
- package/references/templates/technical_specification.md +37 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
+
import { expect } from './_expect-shim.mjs';
|
|
3
|
+
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
parseFrontmatter,
|
|
8
|
+
parseStaleAfter,
|
|
9
|
+
computeToday,
|
|
10
|
+
inspectFile,
|
|
11
|
+
buildIndex,
|
|
12
|
+
checkIndexFreshness,
|
|
13
|
+
} from './check-docs-size.mjs';
|
|
14
|
+
|
|
15
|
+
describe('parseFrontmatter', () => {
|
|
16
|
+
it('extracts scalar fields from valid YAML frontmatter', () => {
|
|
17
|
+
const text = '---\ntype: reference\nmaxLines: 500\nstaleAfter: 30d\n---\n\nbody.';
|
|
18
|
+
const fm = parseFrontmatter(text);
|
|
19
|
+
expect(fm).toEqual({
|
|
20
|
+
type: 'reference',
|
|
21
|
+
maxLines: '500',
|
|
22
|
+
staleAfter: '30d',
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns null when no frontmatter block is present', () => {
|
|
27
|
+
expect(parseFrontmatter('just body text\n')).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('skips lines that do not match key:value pattern', () => {
|
|
31
|
+
const text = '---\ntype: reference\n# stray comment\nmaxLines: 100\n---\n';
|
|
32
|
+
const fm = parseFrontmatter(text);
|
|
33
|
+
expect(fm).toEqual({ type: 'reference', maxLines: '100' });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('parseStaleAfter', () => {
|
|
38
|
+
it('parses Nd into Number', () => {
|
|
39
|
+
expect(parseStaleAfter('7d')).toBe(7);
|
|
40
|
+
expect(parseStaleAfter('30d')).toBe(30);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns null for "never", empty, or undefined', () => {
|
|
44
|
+
expect(parseStaleAfter('never')).toBeNull();
|
|
45
|
+
expect(parseStaleAfter('')).toBeNull();
|
|
46
|
+
expect(parseStaleAfter(undefined)).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns null for invalid formats', () => {
|
|
50
|
+
expect(parseStaleAfter('7days')).toBeNull();
|
|
51
|
+
expect(parseStaleAfter('7')).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('computeToday', () => {
|
|
56
|
+
it('parses YYYY-MM-DD into UTC-midnight Date', () => {
|
|
57
|
+
const d = computeToday('2026-05-24');
|
|
58
|
+
expect(d.toISOString()).toBe('2026-05-24T00:00:00.000Z');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns a Date when todayStr is null (no-throw smoke)', () => {
|
|
62
|
+
const d = computeToday(null);
|
|
63
|
+
expect(d).toBeInstanceOf(Date);
|
|
64
|
+
expect(Number.isNaN(d.getTime())).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('inspectFile', () => {
|
|
69
|
+
let dir;
|
|
70
|
+
|
|
71
|
+
beforeEach(async () => {
|
|
72
|
+
dir = await mkdtemp(join(tmpdir(), 'check-docs-size-test-'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(async () => {
|
|
76
|
+
await rm(dir, { recursive: true, force: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('reports no errors and no warnings for an in-cap, fresh file', async () => {
|
|
80
|
+
const path = join(dir, 'fresh.md');
|
|
81
|
+
await writeFile(
|
|
82
|
+
path,
|
|
83
|
+
'---\ntype: reference\nlastUpdated: 2026-05-24\nstaleAfter: 30d\nmaxLines: 100\n---\n\n# OK\n',
|
|
84
|
+
);
|
|
85
|
+
const result = await inspectFile(path, computeToday('2026-05-24'));
|
|
86
|
+
expect(result.errors).toEqual([]);
|
|
87
|
+
expect(result.warnings).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('reports an error when lineCount > maxLines', async () => {
|
|
91
|
+
const path = join(dir, 'big.md');
|
|
92
|
+
const body = '\n'.repeat(20); // 20 lines of body → total ~26
|
|
93
|
+
await writeFile(
|
|
94
|
+
path,
|
|
95
|
+
`---\ntype: reference\nlastUpdated: 2026-05-24\nstaleAfter: 30d\nmaxLines: 5\n---\n\n# Too big${body}`,
|
|
96
|
+
);
|
|
97
|
+
const result = await inspectFile(path, computeToday('2026-05-24'));
|
|
98
|
+
expect(result.errors.some((e) => /lines > maxLines/.test(e))).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('reports an error when frontmatter is missing maxLines', async () => {
|
|
102
|
+
const path = join(dir, 'no-cap.md');
|
|
103
|
+
await writeFile(path, '---\ntype: reference\nlastUpdated: 2026-05-24\n---\n\nbody.\n');
|
|
104
|
+
const result = await inspectFile(path, computeToday('2026-05-24'));
|
|
105
|
+
expect(result.errors).toContain('frontmatter missing maxLines');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('reports a warning when lastUpdated is older than staleAfter window', async () => {
|
|
109
|
+
const path = join(dir, 'stale.md');
|
|
110
|
+
await writeFile(
|
|
111
|
+
path,
|
|
112
|
+
'---\ntype: reference\nlastUpdated: 2026-01-01\nstaleAfter: 30d\nmaxLines: 100\n---\n\nbody.\n',
|
|
113
|
+
);
|
|
114
|
+
const result = await inspectFile(path, computeToday('2026-05-24'));
|
|
115
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
116
|
+
expect(result.warnings[0]).toMatch(/staleAfter/);
|
|
117
|
+
expect(result.errors).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('reports a single error when frontmatter is missing entirely', async () => {
|
|
121
|
+
const path = join(dir, 'no-fm.md');
|
|
122
|
+
await writeFile(path, '# Just a body, no frontmatter.\n');
|
|
123
|
+
const result = await inspectFile(path, computeToday('2026-05-24'));
|
|
124
|
+
expect(result.frontmatter).toBeNull();
|
|
125
|
+
expect(result.errors).toContain('missing YAML frontmatter');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Synthetic row matching the shape produced by `inspectFile` + `formatRow`.
|
|
130
|
+
const makeRow = (path, overrides = {}) => ({
|
|
131
|
+
path,
|
|
132
|
+
lineCount: 50,
|
|
133
|
+
frontmatter: { type: 'reference', maxLines: '100', lastUpdated: '2026-05-29', staleAfter: '30d' },
|
|
134
|
+
errors: [],
|
|
135
|
+
warnings: [],
|
|
136
|
+
...overrides,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('buildIndex', () => {
|
|
140
|
+
it('is deterministic, sorts rows by path, and excludes index.md itself', () => {
|
|
141
|
+
const rows = [
|
|
142
|
+
makeRow('docs/ai/index.md'),
|
|
143
|
+
makeRow('docs/ai/b.md'),
|
|
144
|
+
makeRow('docs/ai/a.md'),
|
|
145
|
+
];
|
|
146
|
+
const out = buildIndex(rows, '2026-05-29');
|
|
147
|
+
expect(out).toBe(buildIndex(rows, '2026-05-29')); // deterministic
|
|
148
|
+
expect(out).not.toMatch(/\[`index\.md`\]/); // index.md row excluded
|
|
149
|
+
expect(out.indexOf('a.md')).toBeLessThan(out.indexOf('b.md')); // sorted
|
|
150
|
+
expect(out).toMatch(/lastUpdated: 2026-05-29/); // header date is the argument
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// `checkIndexFreshness` drives the `--check-index` exit code:
|
|
155
|
+
// fresh === true → script exits 0
|
|
156
|
+
// fresh === false → script exits 1 ("index stale, regenerate with --write-index")
|
|
157
|
+
describe('checkIndexFreshness', () => {
|
|
158
|
+
const rows = [makeRow('docs/ai/a.md'), makeRow('docs/ai/b.md')];
|
|
159
|
+
|
|
160
|
+
it('reports fresh when on-disk matches the regenerated index (→ exit 0)', () => {
|
|
161
|
+
const onDisk = buildIndex(rows, '2026-05-29');
|
|
162
|
+
expect(checkIndexFreshness(rows, onDisk).fresh).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('reports stale when a source row drifted, e.g. line count changed (→ exit 1)', () => {
|
|
166
|
+
const onDisk = buildIndex(rows, '2026-05-29');
|
|
167
|
+
const drifted = [makeRow('docs/ai/a.md', { lineCount: 999 }), makeRow('docs/ai/b.md')];
|
|
168
|
+
expect(checkIndexFreshness(drifted, onDisk).fresh).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('reports stale when index.md is missing entirely (→ exit 1)', () => {
|
|
172
|
+
expect(checkIndexFreshness(rows, null).fresh).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('does NOT flag stale on a mere day-rollover with unchanged content (uses on-disk header date)', () => {
|
|
176
|
+
const onDisk = buildIndex(rows, '2026-05-01'); // index regenerated weeks ago
|
|
177
|
+
// Same source rows, "today" is later — content unchanged, so it must stay fresh.
|
|
178
|
+
expect(checkIndexFreshness(rows, onDisk).fresh).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Idempotent installer for the project's git hooks.
|
|
3
|
+
//
|
|
4
|
+
// Installs `.git/hooks/pre-commit` running the docs cap-validator + index-freshness
|
|
5
|
+
// gate + changelog/issues rotation-freshness checks + the `scripts/` test
|
|
6
|
+
// suite, so docs files cannot drift over their declared `maxLines`, the auto-generated
|
|
7
|
+
// `index.md` navigator cannot silently fall out of sync, stale archive entries never
|
|
8
|
+
// reach a commit, and regressions to the scripts themselves are caught at commit time.
|
|
9
|
+
//
|
|
10
|
+
// Package-manager-agnostic: the hook calls the scripts via `node` directly (no pnpm/npm
|
|
11
|
+
// assumption). Re-running is safe — the script detects a previously installed hook via
|
|
12
|
+
// the MAGIC_MARKER comment and rewrites only that file.
|
|
13
|
+
//
|
|
14
|
+
// To bypass once (only when truly justified): `git commit --no-verify`.
|
|
15
|
+
|
|
16
|
+
import { writeFile, readFile, mkdir, chmod } from 'node:fs/promises';
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { dirname, resolve, basename } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
const ROOT = resolve(__dirname, '..');
|
|
24
|
+
const HOOKS_DIR = resolve(ROOT, '.git/hooks');
|
|
25
|
+
const PRE_COMMIT_PATH = resolve(HOOKS_DIR, 'pre-commit');
|
|
26
|
+
|
|
27
|
+
const readProjectName = () => {
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8'));
|
|
30
|
+
if (pkg.name) return pkg.name;
|
|
31
|
+
} catch {
|
|
32
|
+
/* no package.json — fall back to repo dir basename */
|
|
33
|
+
}
|
|
34
|
+
return basename(ROOT);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const MAGIC_MARKER = `# ${readProjectName()}:install-git-hooks.mjs`;
|
|
38
|
+
|
|
39
|
+
const PRE_COMMIT_CONTENT = `#!/usr/bin/env bash
|
|
40
|
+
${MAGIC_MARKER}
|
|
41
|
+
# Auto-installed by scripts/install-git-hooks.mjs (run it from "prepare" or by hand).
|
|
42
|
+
# Runs the docs cap-validator + index-freshness gate + rotation-freshness checks
|
|
43
|
+
# + the scripts/ test suite before every commit, so files cannot drift over their
|
|
44
|
+
# declared maxLines, the auto-generated index.md cannot silently go stale, stale
|
|
45
|
+
# archive entries never reach a commit, and regressions to the scripts are caught.
|
|
46
|
+
set -e
|
|
47
|
+
node scripts/check-docs-size.mjs
|
|
48
|
+
node scripts/check-docs-size.mjs --check-index
|
|
49
|
+
node scripts/archive-changelog.mjs --check
|
|
50
|
+
node scripts/archive-issues.mjs --check
|
|
51
|
+
node --test scripts/*.test.mjs
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const main = async () => {
|
|
55
|
+
if (!existsSync(resolve(ROOT, '.git'))) {
|
|
56
|
+
console.log('[install-git-hooks] .git directory not found — skipping (not a git checkout).');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await mkdir(HOOKS_DIR, { recursive: true });
|
|
60
|
+
|
|
61
|
+
if (existsSync(PRE_COMMIT_PATH)) {
|
|
62
|
+
const existing = await readFile(PRE_COMMIT_PATH, 'utf8');
|
|
63
|
+
if (existing.includes(MAGIC_MARKER) && existing === PRE_COMMIT_CONTENT) {
|
|
64
|
+
console.log('[install-git-hooks] pre-commit already up to date.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!existing.includes(MAGIC_MARKER)) {
|
|
68
|
+
console.warn(
|
|
69
|
+
'[install-git-hooks] WARNING: .git/hooks/pre-commit exists and was not installed by this script. Refusing to overwrite — remove or merge it manually.',
|
|
70
|
+
);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await writeFile(PRE_COMMIT_PATH, PRE_COMMIT_CONTENT, 'utf8');
|
|
76
|
+
await chmod(PRE_COMMIT_PATH, 0o755);
|
|
77
|
+
console.log('[install-git-hooks] installed .git/hooks/pre-commit (docs caps + index freshness + archive checks + scripts/ tests).');
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
main().catch((err) => {
|
|
81
|
+
console.error(err);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# AI Agent Algorithm — {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
> **Execution protocol for AI agents working on this project.**
|
|
4
|
+
> **This file is your entry point. Read it first, then follow the Memory Map.**
|
|
5
|
+
> `AGENTS.md` is the cross-agent standard — Codex / Cursor / Windsurf / Copilot read it natively. Tool-specific aliases (e.g. `CLAUDE.md` for Claude Code) are symlinks to this file — single source, no duplication.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🧭 Memory Map
|
|
10
|
+
|
|
11
|
+
All project knowledge lives in `docs/ai/`. Layered, lazy-loaded context:
|
|
12
|
+
|
|
13
|
+
- **Always-loaded** — this file + [`docs/ai/index.md`](./docs/ai/index.md) (auto-generated navigator).
|
|
14
|
+
- **On-demand** — read a specific `docs/ai/` file only when its "Read When" applies.
|
|
15
|
+
- **Hierarchical** — subdirectory `AGENTS.md` files (with a `CLAUDE.md` symlink for Claude Code) load automatically when you work in that folder.
|
|
16
|
+
- **Archive** — `docs/ai/history/` (rolling: HOT changelog → WARM `recent.md` → COLD per-month).
|
|
17
|
+
|
|
18
|
+
| File | Read When... | Update When... |
|
|
19
|
+
|------|--------------|----------------|
|
|
20
|
+
| [`docs/ai/handover.md`](./docs/ai/handover.md) | **Start of every session** | End of session if context changed |
|
|
21
|
+
| [`docs/ai/active_plan.md`](./docs/ai/active_plan.md) | Picking next task | Completing a task |
|
|
22
|
+
| [`docs/ai/current_state.md`](./docs/ai/current_state.md) | Need system overview | After feature completion |
|
|
23
|
+
| [`docs/ai/technical_specification.md`](./docs/ai/technical_specification.md) | App overview & data models | Data-model changes |
|
|
24
|
+
| [`docs/ai/pages/index.md`](./docs/ai/pages/index.md) | Understanding a page | Page behaviour changes |
|
|
25
|
+
| [`docs/ai/architecture.md`](./docs/ai/architecture.md) | Understanding structure | Architecture changes |
|
|
26
|
+
| [`docs/ai/known_issues.md`](./docs/ai/known_issues.md) | Debugging | New issue discovered |
|
|
27
|
+
| [`docs/ai/decisions.md`](./docs/ai/decisions.md) | Considering alternatives | New ADR |
|
|
28
|
+
| [`docs/ai/changelog.md`](./docs/ai/changelog.md) | Reviewing history | After each session |
|
|
29
|
+
| [`docs/ai/env_commands.md`](./docs/ai/env_commands.md) | Need a command | Commands change |
|
|
30
|
+
| [`docs/ai/tech_reference.md`](./docs/ai/tech_reference.md) | Need a pattern | New reusable pattern |
|
|
31
|
+
| [`docs/ai/agent_rules.md`](./docs/ai/agent_rules.md) | **Before any code change** | Protocol changes |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🚀 Session Protocols
|
|
36
|
+
|
|
37
|
+
Start-of-session, during-work, and task-completion procedures live in [`docs/ai/agent_rules.md`](./docs/ai/agent_rules.md) §1. **Read it before any code change.**
|
|
38
|
+
|
|
39
|
+
Planning (plan files, vocabulary, lifecycle, mandatory Cleanup) → the project's planning skill / `docs/ai/agent_rules.md` §"Planning Workflow".
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🚫 Hard Constraints
|
|
44
|
+
|
|
45
|
+
> Adapt to this stack — remove rows that don't apply. Style rules should be linter-enforced, not prose.
|
|
46
|
+
|
|
47
|
+
| Rule | Enforcement |
|
|
48
|
+
|------|-------------|
|
|
49
|
+
| No `export default` (named exports only) | Linter |
|
|
50
|
+
| No `any` / no unsafe casts | Linter / type-checker |
|
|
51
|
+
| Functional style (no mutation in app code) | Linter |
|
|
52
|
+
| No single-letter variables | Code review |
|
|
53
|
+
| Interactive elements semantic (button/link, not div+onClick) | Linter / a11y |
|
|
54
|
+
| No hardcoded colors — design tokens only | Code review |
|
|
55
|
+
| No business logic in components → hooks/services | Architecture review |
|
|
56
|
+
| No changes without tests (TDD) | Required |
|
|
57
|
+
| Check page docs before changes; update them after | Process |
|
|
58
|
+
| Ask user before committing | Process |
|
|
59
|
+
| Every page has an HTML-validity / a11y E2E test | Required |
|
|
60
|
+
| **No silent failures** — structured logging on every rejected action | Required |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 💡 Quick Commands
|
|
65
|
+
|
|
66
|
+
| Need | Command |
|
|
67
|
+
|------|---------|
|
|
68
|
+
| Dev server | `{{DEV_COMMAND}}` |
|
|
69
|
+
| All checks | `{{LINT}} && {{TYPECHECK}} && {{TEST}}` |
|
|
70
|
+
| E2E tests | `{{E2E_COMMAND}}` |
|
|
71
|
+
| Docs caps + index freshness | `{{DOCS_CHECK}} && {{DOCS_INDEX_CHECK}}` |
|
|
72
|
+
| Find pattern | `grep -r "pattern" src/` |
|
|
73
|
+
|
|
74
|
+
Full reference → [`docs/ai/env_commands.md`](./docs/ai/env_commands.md).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
**Rule:** This file stays CONCISE (≤100 lines). Details go to `docs/ai/`. Never bloat this file.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: state
|
|
3
|
+
lastUpdated: {{DATE}}
|
|
4
|
+
scope: sprint
|
|
5
|
+
staleAfter: 30d
|
|
6
|
+
owner: active_plan
|
|
7
|
+
maxLines: 60
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Active Plan
|
|
11
|
+
|
|
12
|
+
> Tasks ordered by priority. ONE task at a time. Mark done immediately.
|
|
13
|
+
|
|
14
|
+
## Immediate Priority
|
|
15
|
+
|
|
16
|
+
### Task 1 — Fill domain-specific docs after first real work
|
|
17
|
+
**Why:** bootstrap seeded structure; real content comes from the first task.
|
|
18
|
+
**Acceptance Criteria:**
|
|
19
|
+
- [ ] `current_state.md` / `architecture.md` reflect reality
|
|
20
|
+
- [ ] First page spec created under `pages/`
|
|
21
|
+
- [ ] Tests passing, docs updated
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Next Up (queue)
|
|
26
|
+
|
|
27
|
+
- [ ] TODO — short reason
|
|
28
|
+
|
|
29
|
+
## Parking Lot (ideas, not committed)
|
|
30
|
+
|
|
31
|
+
- TODO
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: protocol
|
|
3
|
+
lastUpdated: {{DATE}}
|
|
4
|
+
scope: permanent
|
|
5
|
+
staleAfter: 90d
|
|
6
|
+
owner: none
|
|
7
|
+
maxLines: 150
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# AI Agent Rules & Self-Review Protocol — {{PROJECT_NAME}}
|
|
11
|
+
|
|
12
|
+
Every AI agent working on this project **must** adhere to this protocol before writing code, modifying files, or presenting solutions.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Session Protocols
|
|
17
|
+
|
|
18
|
+
### 1.1. Start of Session
|
|
19
|
+
Read in order, then confirm before starting:
|
|
20
|
+
1. `docs/ai/handover.md` — where we left off.
|
|
21
|
+
2. `docs/ai/active_plan.md` — pick ONE task from "Immediate Priority".
|
|
22
|
+
3. Confirm with the user: *"I'm taking task X. Confirm?"*
|
|
23
|
+
|
|
24
|
+
### 1.2. During Work
|
|
25
|
+
**Before any feature:** read the relevant page spec (`docs/ai/pages/<page>.md`). If behaviour changes, update the spec FIRST so docs and code never diverge.
|
|
26
|
+
|
|
27
|
+
**For every code change:**
|
|
28
|
+
1. Grep for similar implementations — reuse existing patterns.
|
|
29
|
+
2. Check the design-system layer for an existing component; if missing, add it there FIRST, then use it.
|
|
30
|
+
3. Verify changes align with `docs/ai/pages/<page>.md`; for a new page, create a full spec.
|
|
31
|
+
4. Follow §2 (Self-Review): functional style, named exports, full variable names, no magic literals.
|
|
32
|
+
5. Write/update tests FIRST (TDD): unit for pure functions, E2E for user flows.
|
|
33
|
+
6. Run quality checks: lint, type-check, tests.
|
|
34
|
+
|
|
35
|
+
### 1.3. Task Completion
|
|
36
|
+
Before claiming "done":
|
|
37
|
+
1. Run all quality gates (lint + type-check + tests) — all green.
|
|
38
|
+
2. Update docs: `current_state.md` (feature ready), `changelog.md` (entry), `handover.md` (**REPLACE** the last-session block — session delta, never append; older deltas live in `changelog.md` → `history/`), `pages/<page>.md` (matches implementation). Only bump "Last Updated" when content actually changed.
|
|
39
|
+
3. Run the docs cap-validator + index-freshness gate (pre-commit also enforces). On failure: trim the offending file, or run the changelog rotation if the offender is `changelog.md`.
|
|
40
|
+
4. If the work executed a plan file — run that plan's final **Phase: Cleanup** (see the planning skill / §5). Without it the plan is not done.
|
|
41
|
+
5. **Ask before committing** (§4): report lint / type-check / test counts + docs status, then wait for explicit approval. DO NOT auto-commit.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 2. Self-Review Checklist
|
|
46
|
+
|
|
47
|
+
Before proposing changes or committing, review against:
|
|
48
|
+
|
|
49
|
+
### 2.1. Real-World Context
|
|
50
|
+
- Respects the user's locale (translations, decimal separators, encodings/BOM for non-ASCII exports).
|
|
51
|
+
|
|
52
|
+
### 2.2. Clean Code
|
|
53
|
+
- **No magic literals** — extract string/numeric constants to named consts at module level.
|
|
54
|
+
- **DRY** — no duplicated logic.
|
|
55
|
+
|
|
56
|
+
### 2.3. Strict Compliance
|
|
57
|
+
- Only `const` (no `let`); no classes — pure functions, closures, modules.
|
|
58
|
+
- Components as arrow functions; no unsafe `any` / casts.
|
|
59
|
+
- Functions start with a verb (`computeTotal`, `getList`).
|
|
60
|
+
|
|
61
|
+
### 2.4. Quality Gates
|
|
62
|
+
- Always run type-checker, linter, and all tests before committing.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Token & Session Optimization
|
|
67
|
+
|
|
68
|
+
Split a complex task across sessions for **focus and review hygiene**, not because the window is small. Modern long-context models (e.g. Opus 4.8) hold large working sets well, and a stable always-loaded layer (`AGENTS.md` + `index.md`) stays prompt-cache-warm across turns — split too eagerly and you pay re-boot + cache-miss cost for nothing.
|
|
69
|
+
|
|
70
|
+
- **Split (separate sessions)** when the *change* is large enough to deserve an isolated review checkpoint: creates/deletes a source or test file, touches several files, alters a dependency / core config / data model / routes, or needs new E2E tests. These are review-hygiene triggers, independent of context size.
|
|
71
|
+
- **Run inline** for small, self-contained work: a few files, no new files, no dependency/schema/route change, light discussion (typos, tweaks, single-line fixes, test additions).
|
|
72
|
+
- **Context size is a soft, secondary signal** — split only when the working context is genuinely large *relative to the window* or you notice degraded recall, never at a fixed token count. Prefer the planning skill's **session-continuity heuristic**: keep going in-session when the accumulated context IS the execution payload (targeted-deep reads of the exact files you'll edit); split when it was broad fan-out noise.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. User Interaction
|
|
77
|
+
|
|
78
|
+
1. **Don't rush to commit.** Prepare changes, run local quality checks, report progress with test outcomes.
|
|
79
|
+
2. **Get explicit approval** before staging/committing or moving to the next phase.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 5. Planning Workflow
|
|
84
|
+
|
|
85
|
+
All plan-file rules — vocabulary (Plan → Phase → Step → Substep), lifecycle (`docs/plans/<slug>.md`, gitignored, never committed), the mandatory final **Phase: Cleanup**, the `docs/plans/queue.md` series-index, "all work in plans", the plan-then-execute split — live in the project's planning skill. It is the single source of truth and overrides the generic `writing-plans` skill.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
lastUpdated: {{DATE}}
|
|
4
|
+
scope: permanent
|
|
5
|
+
staleAfter: 90d
|
|
6
|
+
owner: none
|
|
7
|
+
maxLines: 350
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Architecture
|
|
11
|
+
|
|
12
|
+
> How the code is organized. Layers, boundaries, where new code goes.
|
|
13
|
+
|
|
14
|
+
## Layers
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
UI Components ← {{src/components/}}
|
|
18
|
+
↓
|
|
19
|
+
Hooks / Composition ← {{src/hooks/}}
|
|
20
|
+
↓
|
|
21
|
+
Services (pure) ← {{src/services/}}
|
|
22
|
+
↓
|
|
23
|
+
State / Storage ← {{src/store/ or src/data/}}
|
|
24
|
+
↓
|
|
25
|
+
Types / Schemas ← {{src/types/}}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Module Boundaries
|
|
29
|
+
|
|
30
|
+
- **Components** — render only. No fetching, no business logic.
|
|
31
|
+
- **Hooks** — orchestration; combine services + state.
|
|
32
|
+
- **Services** — pure functions. Easy to test.
|
|
33
|
+
- **State** — single source of truth per domain.
|
|
34
|
+
|
|
35
|
+
## File Tree (top level)
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
{{FILL FROM REAL src/}}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Extension Points
|
|
42
|
+
|
|
43
|
+
- Add a new page → {{where + what to update}}
|
|
44
|
+
- Add a new domain entity → {{where}}
|
|
45
|
+
- Add a new shared component → {{where + docs to update}}
|
|
46
|
+
|
|
47
|
+
## Split policy (when this file nears its cap)
|
|
48
|
+
|
|
49
|
+
At ~90% of `maxLines`, split the deepest detail into `architecture-details.md` and keep this file high-level. Record the split as an ADR. Bumping the cap instead of splitting is a conscious exception, justified in that ADR.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: history
|
|
3
|
+
lastUpdated: {{DATE}}
|
|
4
|
+
scope: permanent
|
|
5
|
+
staleAfter: never
|
|
6
|
+
owner: none
|
|
7
|
+
maxLines: 700
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# AI Session Changelog
|
|
11
|
+
|
|
12
|
+
> One entry per session. Newest at the top. Entries roll off to `history/recent.md` (WARM) then `history/YYYY-MM.md` (COLD) via the archive script.
|
|
13
|
+
> Heading format is load-bearing for rotation: `## YYYY.MM.DD — <title>`.
|
|
14
|
+
|
|
15
|
+
## {{DATE}} — Bootstrap
|
|
16
|
+
|
|
17
|
+
**Goal:** Initialise the AI-agent memory system.
|
|
18
|
+
**Changes:**
|
|
19
|
+
- Created `AGENTS.md` (entry point) + `CLAUDE.md` symlink.
|
|
20
|
+
- Created `docs/ai/` with the spec files + `pages/`.
|
|
21
|
+
- Installed the docs cap-validator / index-freshness / archive scripts + pre-commit hook.
|
|
22
|
+
- Recorded the real stack, scripts, and architecture into the relevant files.
|
|
23
|
+
|
|
24
|
+
**Quality gates:** n/a (no code changes).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: state
|
|
3
|
+
lastUpdated: {{DATE}}
|
|
4
|
+
scope: permanent
|
|
5
|
+
staleAfter: 30d
|
|
6
|
+
owner: current_state
|
|
7
|
+
maxLines: 120
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Current State
|
|
11
|
+
|
|
12
|
+
> Snapshot of what's built and what works. Updated after each feature completion.
|
|
13
|
+
|
|
14
|
+
## Stack
|
|
15
|
+
|
|
16
|
+
| Layer | Tool |
|
|
17
|
+
|-------|------|
|
|
18
|
+
| Language | {{LANGUAGE}} |
|
|
19
|
+
| Framework | {{FRAMEWORK}} |
|
|
20
|
+
| Package manager | {{PACKAGE_MANAGER}} |
|
|
21
|
+
| Test runner | {{TEST_RUNNER}} |
|
|
22
|
+
| Linter | {{LINTER}} |
|
|
23
|
+
|
|
24
|
+
## Feature Status
|
|
25
|
+
|
|
26
|
+
| Feature | Status | Notes |
|
|
27
|
+
|---------|--------|-------|
|
|
28
|
+
| AI memory system | ✅ Ready | Bootstrapped {{DATE}} |
|
|
29
|
+
| {{FEATURE}} | 🟡 In progress / ⛔ Blocked | {{note}} |
|
|
30
|
+
|
|
31
|
+
## Quality Gates
|
|
32
|
+
|
|
33
|
+
- lint: {{N}} errors / {{M}} warnings
|
|
34
|
+
- type-check: {{clean / N errors}}
|
|
35
|
+
- unit tests: {{N passed in M files}}
|
|
36
|
+
- E2E tests: {{N passed}}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
lastUpdated: {{DATE}}
|
|
4
|
+
scope: permanent
|
|
5
|
+
staleAfter: never
|
|
6
|
+
owner: none
|
|
7
|
+
maxLines: 500
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Architecture Decision Records (ADRs)
|
|
11
|
+
|
|
12
|
+
> Every significant choice that has long-term consequences. Newest at the bottom. Link related ADRs with `[[AD-XXX]]`.
|
|
13
|
+
|
|
14
|
+
## AD-001 — Adopt AI-agent memory system (`docs/ai/`)
|
|
15
|
+
|
|
16
|
+
**Date:** {{DATE}}
|
|
17
|
+
**Status:** Accepted
|
|
18
|
+
|
|
19
|
+
**Context.** Multi-session AI work loses context between runs. Without a structured handover, each new session re-reads code, re-discovers decisions, and repeats past mistakes.
|
|
20
|
+
|
|
21
|
+
**Decision.** Adopt a Memory Map in `AGENTS.md` (entry point — the cross-agent standard; tool aliases like `CLAUDE.md` symlink to it) + structured files under `docs/ai/`. Define three protocols (Start / During / Complete). Enforce frontmatter caps + index freshness + 3-tier archive via a pre-commit hook. Deployed via the `agent-workflow-kit` skill.
|
|
22
|
+
|
|
23
|
+
**Rationale.** Single entry + structured spec files = constant boot-up cost regardless of project size. ADRs prevent litigating the same decision twice. `pages/<page>.md` keeps behaviour canonical (docs > assumptions). Caps + archive keep files scannable as history grows.
|
|
24
|
+
|
|
25
|
+
**Consequences.**
|
|
26
|
+
- ➕ Faster session start, less drift between agents.
|
|
27
|
+
- ➕ ADRs as institutional memory; honest `known_issues.md`.
|
|
28
|
+
- ➖ Discipline cost: docs updated alongside code.
|
|
29
|
+
- ➖ A set of markdown files + scripts to maintain.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## AD-002 — {{Next decision}}
|
|
34
|
+
|
|
35
|
+
**Date:** {{DATE}}
|
|
36
|
+
**Status:** Proposed / Accepted / Superseded
|
|
37
|
+
|
|
38
|
+
**Context.** {{...}}
|
|
39
|
+
**Decision.** {{...}}
|
|
40
|
+
**Consequences.** {{...}}
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
> When this file nears ~90% of its cap, move the oldest Accepted/Superseded ADRs to `decisions-archive.md` (keep the IDs + a one-line pointer here) and record the split. Bumping the cap instead of splitting is a conscious exception, justified in an ADR.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
lastUpdated: {{DATE}}
|
|
4
|
+
scope: permanent
|
|
5
|
+
staleAfter: 90d
|
|
6
|
+
owner: none
|
|
7
|
+
maxLines: 120
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Environment & Commands
|
|
11
|
+
|
|
12
|
+
> Single reference for all dev/build/test/lint commands. (Mature projects may fold this into a project command skill.)
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
{{install command}}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Daily Loop
|
|
21
|
+
|
|
22
|
+
| Need | Command |
|
|
23
|
+
|------|---------|
|
|
24
|
+
| Dev server | `{{DEV_COMMAND}}` |
|
|
25
|
+
| Build | `{{BUILD_COMMAND}}` |
|
|
26
|
+
| Lint | `{{LINT}}` |
|
|
27
|
+
| Type-check | `{{TYPECHECK}}` |
|
|
28
|
+
| Unit tests | `{{TEST}}` |
|
|
29
|
+
| Unit tests (one file) | `{{TEST}} {{path/file}}` |
|
|
30
|
+
| E2E tests | `{{E2E_COMMAND}}` |
|
|
31
|
+
| Docs caps | `{{DOCS_CHECK}}` |
|
|
32
|
+
| Docs index (regenerate) | `{{DOCS_INDEX}}` |
|
|
33
|
+
| Docs index (freshness gate) | `{{DOCS_INDEX_CHECK}}` |
|
|
34
|
+
| Changelog rotation | `{{DOCS_ARCHIVE}}` |
|
|
35
|
+
| All checks (pre-commit) | `{{LINT}} && {{TYPECHECK}} && {{TEST}}` |
|
|
36
|
+
|
|
37
|
+
## Git Conventions
|
|
38
|
+
|
|
39
|
+
- Branches: `{{convention}}`
|
|
40
|
+
- Commit style: `{{e.g. Conventional Commits}}`
|
|
41
|
+
- PR target: `{{branch}}`
|