Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Less context to repeat, clear task tracking, and decisions preserved between sessions.
Choose only the modules you need and adapt them to your project.

**Claude Code · Codex · Copilot · 8 optional modules · Customizable**
**Claude Code · Codex · Copilot · 9 optional modules · Customizable**

## Supported coding agents

Expand Down Expand Up @@ -128,6 +128,7 @@ project's `.gitignore` is not touched. Hooks stay silent in clones that do not h
| Reporting (`reporting`) | Weekly goals and progress reports |
| Exploration (`exploration`) | Help clarifying ideas before development |
| Learning (`learning`) | Explanations and guidance to help you learn |
| Knowledge (`knowledge`) | Indexes of your app's flows and domain facts, read one section at a time |

For a custom first installation:

Expand Down
10 changes: 9 additions & 1 deletion docs/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ or execute anything. `--module DIRECTORY` selects that module; `--modules id,id`
Repeat either source option to supply more directories. Required dependencies are resolved from
installed modules and available catalogs and printed in the plan. No network download occurs.
Use `--profile minimal|continuity|complete` during initial installation; Complete has a fixed list
of eight official modules and never automatically activates external discoveries.
of nine official modules and never automatically activates external discoveries.

## Manifest version 1

Expand Down Expand Up @@ -86,6 +86,14 @@ field, and `links` requires every relative Markdown link to name an existing fil
reported by the doctor without failing it, and installation, add and update skip it. It checks no
external services and makes no claim about the agent following instructions.

## Knowledge module

`knowledge` installs two editable indexes: `{{TASKS_DIR}}/APP.md` routes code flows
(flow → `doc/app/<flow>.md` → keywords) and `{{TASKS_DIR}}/DOC.md` routes verified domain facts
(theme → `doc/<theme>.md` → when to read). They hold project-specific knowledge the model cannot
know, never generic stack documentation. The doctor warns when an index exceeds 8 KiB, a theme
exceeds 16 KiB or an index links to a missing file.

## Preservation and lifecycle

The installed `{{KIT_DIR}}/kit.json` records modules, versions, selected IDs, settings, file owners and
Expand Down
12 changes: 12 additions & 0 deletions modules/knowledge/files/APP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Application flows — index

Code navigation map, separate from the domain facts in [DOC.md](DOC.md). One file per flow in
`doc/app/`. Find a keyword under "When to read", then open only that flow and its matching `## `
section. Do not load every flow. A flow links to its implementation files; rules and invariants
stay in their owning documents.

| Flow | File | When to read (keywords) |
|---|---|---|

Row format: flow name, a relative link to `doc/app/<flow>.md`, then the symbols, files and
symptoms that should lead a reader to that flow.
15 changes: 15 additions & 0 deletions modules/knowledge/files/DOC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Domain docs — index

Verified domain facts (source code, specifications, runtime observations), one file per theme in
`doc/`. This index routes to a file; it does not describe its content. Read only the matching
theme, never this whole index plus every theme.

1. Find the row by responsibility under "When to read".
2. Exact symbol not listed → `grep -ril '<symbol>' {{TASKS_DIR}}/doc`; theme bodies are the only
source of symbols, the index does not copy them.
3. Large theme → `grep -n '^## '` on the file, then read only the matching section's line range.

| Theme | File | When to read |
|---|---|---|

Row format: theme name, a relative link to `doc/<theme>.md`, then the questions it answers.
64 changes: 64 additions & 0 deletions modules/knowledge/module.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"schemaVersion": 1,
"id": "knowledge",
"version": "1.0.0",
"description": "Project knowledge indexes: application flows and domain facts",
"requires": {},
"skills": [],
"files": [
{
"source": "files/APP.md",
"target": "{{TASKS_DIR}}/APP.md",
"editable": true
},
{
"source": "files/DOC.md",
"target": "{{TASKS_DIR}}/DOC.md",
"editable": true
}
],
"rules": [
{
"id": "main",
"source": "rules/main.md",
"point": "session-start"
}
],
"hooks": [],
"checks": [
{
"path": "{{TASKS_DIR}}/APP.md",
"type": "file",
"maxBytes": 8192,
"level": "warn"
},
{
"path": "{{TASKS_DIR}}/DOC.md",
"type": "file",
"maxBytes": 8192,
"level": "warn"
},
{
"pattern": "{{TASKS_DIR}}/doc/app/*.md",
"type": "file",
"maxBytes": 16384,
"level": "warn"
},
{
"pattern": "{{TASKS_DIR}}/doc/*.md",
"type": "file",
"maxBytes": 16384,
"level": "warn"
},
{
"path": "{{TASKS_DIR}}/APP.md",
"type": "links",
"level": "warn"
},
{
"path": "{{TASKS_DIR}}/DOC.md",
"type": "links",
"level": "warn"
}
]
}
4 changes: 4 additions & 0 deletions modules/knowledge/rules/main.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
For code paths start at `${TASKS_DIR}/APP.md`, for domain facts at `${TASKS_DIR}/DOC.md`: read the
matching index row, then only the matching `## ` section of that file, never every theme.
Write a flow doc in `doc/app/` and its index row only when the same flow is investigated a second
time. Never copy index or theme content into CLAUDE.md or AGENTS.md.
2 changes: 1 addition & 1 deletion templates/project/.workflow/runtime/catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync, readdirSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { assertKeys, readJson, safePath } from './paths.mjs';

export const officialModules = ['tasks', 'memory', 'continuity', 'quality', 'bugs', 'reporting', 'exploration', 'learning'];
export const officialModules = ['tasks', 'memory', 'continuity', 'quality', 'bugs', 'reporting', 'exploration', 'learning', 'knowledge'];
const moduleIdPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
const versionPattern = /^\d+\.\d+\.\d+$/;
const dependencyPattern = /^(?:\*|\^?\d+\.\d+\.\d+)$/;
Expand Down
29 changes: 26 additions & 3 deletions tests/modules.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,27 @@ test('doctor warns, without failing, on index links to missing files', context =
assert.match(output, /0 failure\(s\), 2 warning\(s\)/);
});

test('knowledge: indexes installed editable, preserved on repeat add, themes and links warn', context => {
const setup = fixture(context); install(setup);
add(setup, ['--modules', 'knowledge']);
const tasks = join(setup.project, 'planning notes/tasks');
assert.match(read(setup, 'planning notes/tasks/APP.md'), /\| Flow \| File \| When to read/);
assert.match(read(setup, 'planning notes/tasks/DOC.md'), /grep -ril '<symbol>' planning notes\/tasks\/doc/);
assert.match(read(setup, `${kit}/PROJECT_RULES.md`), /<!-- module:knowledge\/main -->\nFor code paths start at `\$\{TASKS_DIR\}\/APP.md`/);
assert.doesNotMatch(read(setup, 'CLAUDE.md'), /APP\.md/);
assert.match(command('bash', [join(setup.project, kit, 'scripts/workflow-doctor.sh')]), /0 failure\(s\), 0 warning\(s\)/);
mkdirSync(join(tasks, 'doc/app'), { recursive: true });
writeFileSync(join(tasks, 'doc/app/login.md'), 'x'.repeat(16385));
writeFileSync(join(tasks, 'APP.md'), '| Login | [login.md](doc/app/login.md) | `signIn` |\n| Cart | [cart.md](doc/app/cart.md) | `addItem` |\n');
const edited = snapshot(setup.project);
add(setup, ['--modules', 'knowledge']);
assert.deepEqual(snapshot(setup.project), edited);
const output = command('bash', [join(setup.project, kit, 'scripts/workflow-doctor.sh')]);
assert.match(output, /warn Size limit exceeded: planning notes\/tasks\/doc\/app\/login.md/);
assert.match(output, /warn Broken link in planning notes\/tasks\/APP.md: doc\/app\/cart.md/);
assert.match(output, /0 failure\(s\), 2 warning\(s\)/);
});

test('external module: added after install, settings, dependencies, mirrors, editable notes and exact repeat', context => {
const setup = fixture(context); install(setup);
const source = join(repository, 'examples/modules/project-notes');
Expand Down Expand Up @@ -190,7 +211,7 @@ test('complete profile, conditional reporting integration, reporting add and bra
const output = command('bash', [join(setup.project, kit, 'hooks/show-handoff.sh')]);
assert.match(output, /todo\/dev-example\/README.md/); assert.match(output, /memory\/dev-example.md/); assert.match(output, /handoff\/dev-example.md/); assert.match(output, /Reporting/);
add(setup, ['--modules', officialModules.join(',')]);
assert.equal(state(setup).modules.length, 8);
assert.equal(state(setup).modules.length, 9);
assert.match(command('bash', [join(setup.project, kit, 'scripts/workflow-doctor.sh')]), /0 failure/);
});

Expand Down Expand Up @@ -371,12 +392,12 @@ test('all official modules have a unique skill owner; all profiles and independe
'wayfinder', 'weekly-report', 'write-task',
];
assert.deepEqual([...owners].sort(), expectedSkills.sort());
for (const moduleId of ['memory', 'tasks', 'bugs', 'reporting', 'learning', 'exploration']) {
for (const moduleId of ['memory', 'tasks', 'bugs', 'reporting', 'learning', 'exploration', 'knowledge']) {
const setup = fixture(context); install(setup, ['--modules', moduleId]);
assert.match(command('bash', [join(setup.project, kit, 'scripts/workflow-doctor.sh')]), /0 failure/);
}
const complete = fixture(context); install(complete, ['--profile', 'complete']);
assert.equal(state(complete).modules.length, 8);
assert.equal(state(complete).modules.length, 9);
});

test('installed skills ship the files their SKILL.md links to', context => {
Expand Down Expand Up @@ -468,6 +489,7 @@ for (const [profile, extra, configuration] of [
['continuity', ['--profile', 'continuity'], ''],
['quality', ['--modules', 'tasks,memory,quality'], ''],
['quality with exports', ['--modules', 'tasks,memory,quality'], 'INSTALL_GLOBAL_SKILLS=true\n'],
['knowledge', ['--modules', 'tasks,memory,knowledge'], ''],
]) test(`update ${profile}: real source and module diff, preserved local data, mirrors, doctor and idempotence`, context => {
const setup = updateFixture(context, extra, configuration);
const initial = state(setup);
Expand All @@ -481,6 +503,7 @@ for (const [profile, extra, configuration] of [
initial.configuration.tasksDirectory + '/todo/local-effort/01-task.md',
initial.configuration.tasksDirectory + '/handoff/dev-local.md',
];
if (initial.modules.some(module => module.id === 'knowledge')) localArtifacts.push(initial.configuration.tasksDirectory + '/APP.md');
for (const path of localArtifacts) {
mkdirSync(resolve(setup.project, path, '..'), { recursive: true });
writeFileSync(join(setup.project, path), 'Local user evidence: do not reset.\n');
Expand Down
Loading