kushi-agents 5.0.4 → 5.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/README.md +17 -0
- package/bin/cli.mjs +63 -1
- package/package.json +1 -1
- package/plugin/agents/kushi.agent.md +3 -2
- package/plugin/instructions/living-wiki.instructions.md +88 -0
- package/plugin/instructions/log-format.instructions.md +78 -0
- package/plugin/instructions/wiki-lint.instructions.md +110 -0
- package/plugin/skills/_shared/Append-StateLog.ps1 +73 -0
- package/plugin/skills/_shared/Update-StateIndex.ps1 +47 -0
- package/plugin/skills/ask-project/SKILL.md +30 -0
- package/plugin/skills/build-state/SKILL.md +18 -2
- package/plugin/skills/lint-state/.created-by-skill-creator +0 -0
- package/plugin/skills/lint-state/SKILL.md +98 -0
- package/plugin/skills/lint-state/evals/evals.json +34 -0
- package/plugin/skills/lint-state/lint.ps1 +218 -0
- package/plugin/skills/self-check/SKILL.md +3 -1
- package/plugin/skills/self-check/run.ps1 +133 -3
- package/plugin/skills/skill-checker/check-skill.ps1 +1 -1
- package/plugin/templates/state/answers.README.md +7 -0
- package/plugin/templates/state/hot.template.md +12 -0
- package/plugin/templates/state/review-queue.template.md +10 -0
- package/src/eval-runner.test.mjs +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
[](https://gim-home.github.io/kushi/)
|
|
8
8
|
[](https://agentskills.io/skill-creation/best-practices)
|
|
9
9
|
|
|
10
|
+
> **v5.1.0 — Living wiki.** Build-state is now incremental: human edits outside `<!-- kushi:auto -->` fences are preserved, contradictions are flagged with Obsidian-compatible callouts (`> [!warning]`), and a new `lint-state` skill monitors wiki health. State/ is a valid [Obsidian](https://obsidian.md) vault — callout syntax, Dataview-compatible frontmatter, and `[[wikilinks]]` all work natively.
|
|
11
|
+
|
|
10
12
|
> **v5.0.1 spec-compliance pass.** Every `plugin/skills/<name>/SKILL.md` follows the [agentskills.io best practices](https://agentskills.io/skill-creation/best-practices) and [optimizing-descriptions](https://agentskills.io/skill-creation/optimizing-descriptions) guides: ≤ 500 lines & ≤ 5000 tokens, load-on-trigger references, top-5 gotchas, checklist orchestrators, validation loops, plan-validate-execute for graph + State writers, and "USE WHEN …" descriptions. Enforced by `self-check -Deep` D30.*.
|
|
11
13
|
|
|
12
14
|
> **Project lineage:** for the *why* behind each release — what built on what, what trade-offs were accepted, what external work inspired which design — see [`docs/genealogy.md`](docs/genealogy.md). Every release MUST add an entry there before tagging (enforced by `self-check` D31.genealogy).
|
|
@@ -74,6 +76,21 @@ Apply [a]ll · [s]elect · [n]one?
|
|
|
74
76
|
|
|
75
77
|
Try it: `npx kushi-agents --clawpilot --profile preview` · How-to: [Two-way ADO update](https://gim-home.github.io/kushi/how-to/two-way-ado-update/) · Roadmap: [`docs/concepts/roadmap.md`](docs/concepts/roadmap.md).
|
|
76
78
|
|
|
79
|
+
## Obsidian compatible
|
|
80
|
+
|
|
81
|
+
`State/` is a valid [Obsidian](https://obsidian.md) vault out of the box:
|
|
82
|
+
|
|
83
|
+
- **Callout syntax** — contradictions use `> [!warning]` / `> [!info]` callouts (native Obsidian rendering).
|
|
84
|
+
- **Dataview compatible** — every State page has YAML frontmatter (`kushi_state_page: true`, `entity_ids`, `related`, timestamps) queryable by [Dataview](https://blacksmithgu.github.io/obsidian-dataview/).
|
|
85
|
+
- **Wikilinks** — cross-references use `[[category/slug]]` form, resolvable by Obsidian's link resolver.
|
|
86
|
+
- **No binary assets required** — all content is plain Markdown. No images, no custom plugins needed.
|
|
87
|
+
|
|
88
|
+
To use: point Obsidian at `<project>/State/` as a vault. The `index.md` serves as the home page, `log.md` is the activity feed, and category folders (`people/`, `decisions/`, etc.) are navigable in the sidebar.
|
|
89
|
+
|
|
90
|
+
> **Note:** Obsidian is optional. State/ works identically without it — the conventions are designed to be tool-agnostic while being Obsidian-first for teams that use it.
|
|
91
|
+
|
|
92
|
+
<!-- Screenshot placeholder: Obsidian rendering of State/ with contradiction callouts visible. No binary screenshots committed — use text descriptions or link to docs site. -->
|
|
93
|
+
|
|
77
94
|
---
|
|
78
95
|
|
|
79
96
|
## Three install profiles
|
package/bin/cli.mjs
CHANGED
|
@@ -15,6 +15,17 @@ if (args.length > 0 && SKILL_VERBS.has(args[0])) {
|
|
|
15
15
|
process.exit(0);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// ── lint verb (v5.1.0+) ──────────────────────────────────────────────────────
|
|
19
|
+
if (args.length > 0 && args[0] === 'lint') {
|
|
20
|
+
const project = args[1] || '';
|
|
21
|
+
if (!project) {
|
|
22
|
+
console.error('\n Usage: kushi lint <project>\n');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
await dispatchLint(project);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
if (args.includes('--help') || args.includes('-h')) {
|
|
19
30
|
console.log(`
|
|
20
31
|
Usage: npx kushi-agents [options]
|
|
@@ -61,13 +72,17 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
61
72
|
Rewrite a skill's description per the optimizer rules.
|
|
62
73
|
review-evals <skill> Render an HTML side-by-side eval-review viewer.
|
|
63
74
|
|
|
75
|
+
Wiki maintenance (v5.1.0+):
|
|
76
|
+
lint <project> Run wiki-lint checks on State/ (contradictions, stale claims, orphans).
|
|
77
|
+
|
|
64
78
|
After install, talk to Kushi:
|
|
65
79
|
bootstrap <project> First-time setup
|
|
66
80
|
refresh <project> Incremental refresh + rebuild State/
|
|
67
81
|
state <project> Re-render State/ from existing Evidence
|
|
68
82
|
consolidate <project> Merge per-user evidence
|
|
69
83
|
status <project> Show run-log
|
|
70
|
-
ask <project> <q> Cited Q&A over Evidence/ (auto-routes)
|
|
84
|
+
ask <project> <q> Cited Q&A over Evidence/ (auto-routes, --file-back to save)
|
|
85
|
+
lint <project> Run wiki-lint checks on State/
|
|
71
86
|
|
|
72
87
|
In VS Code Chat the prefix is "@Kushi". In Clawpilot just say "kushi <verb>".
|
|
73
88
|
`);
|
|
@@ -210,6 +225,53 @@ async function dispatchSkillVerb(verb, rest) {
|
|
|
210
225
|
process.exit(result.status ?? 1);
|
|
211
226
|
}
|
|
212
227
|
|
|
228
|
+
async function dispatchLint(project) {
|
|
229
|
+
const { spawn } = await import('node:child_process');
|
|
230
|
+
const { resolve } = await import('node:path');
|
|
231
|
+
const { fileURLToPath } = await import('node:url');
|
|
232
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
233
|
+
const scriptPath = resolve(__dirname, '..', 'plugin', 'skills', 'lint-state', 'lint.ps1');
|
|
234
|
+
|
|
235
|
+
const cwd = process.cwd();
|
|
236
|
+
const { readdirSync, existsSync } = await import('node:fs');
|
|
237
|
+
let stateDir = '';
|
|
238
|
+
|
|
239
|
+
const evidenceDir = resolve(cwd, project, 'Evidence');
|
|
240
|
+
if (existsSync(evidenceDir)) {
|
|
241
|
+
const aliases = readdirSync(evidenceDir, { withFileTypes: true })
|
|
242
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('_'));
|
|
243
|
+
for (const alias of aliases) {
|
|
244
|
+
const candidate = resolve(evidenceDir, alias.name, 'State');
|
|
245
|
+
if (existsSync(candidate)) { stateDir = candidate; break; }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!stateDir) {
|
|
250
|
+
const direct = resolve(cwd, project, 'State');
|
|
251
|
+
if (existsSync(direct)) { stateDir = direct; }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!stateDir) {
|
|
255
|
+
console.error(`\n Could not find State/ directory for project '${project}'.`);
|
|
256
|
+
console.error(` Looked in: ${evidenceDir}/*/State/ and ${resolve(cwd, project, 'State')}/`);
|
|
257
|
+
console.error(` Run 'kushi state ${project}' first to build State/.\n`);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const child = spawn('pwsh', ['-NoProfile', '-File', scriptPath, '-StateDir', stateDir], {
|
|
262
|
+
stdio: 'inherit',
|
|
263
|
+
cwd: resolve(__dirname, '..'),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return new Promise((res, rej) => {
|
|
267
|
+
child.on('close', (code) => {
|
|
268
|
+
if (code !== 0) rej(new Error(`lint-state exited with code ${code}`));
|
|
269
|
+
else res();
|
|
270
|
+
});
|
|
271
|
+
child.on('error', rej);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
213
275
|
function pickFlag(args, flag) {
|
|
214
276
|
const idx = args.indexOf(flag);
|
|
215
277
|
if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kushi-agents",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Install Kushi — multi-source project evidence agent with Comprehensive Structured Capture (CSC) into weekly-only files across Email, Teams, OneNote, Loop, SharePoint, Meetings, CRM, ADO. Meetings retain a sibling verbatim/ audit folder. WorkIQ-only for M365 sources (Graph / m365_* FORBIDDEN as fallbacks; user-paste is first-class). Host-agnostic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,8 +17,8 @@ Kushi ships in three profiles. The installed profile is recorded in `kushi-insta
|
|
|
17
17
|
| Profile | What's installed | Verbs available |
|
|
18
18
|
|---|---|---|
|
|
19
19
|
| `core` | Aggregator only: `setup`, `pull-*`, `consolidate-evidence`, `aggregate-project`, `ask-project`, `project-status`, `vertex-link`, `emit-vertex`, `self-check`, `eval`, `intro` | `setup`, `aggregate`, `consolidate`, `status`, `pull`, `ask`, `vertex-link`, `emit-vertex` |
|
|
20
|
-
| `standard` *(default)* | core + `bootstrap-project`, `refresh-project`, `fde-intake`, `fde-report`, `fde-triage` + FDE reference pack | core + `bootstrap`, `refresh`, `fde-intake`, `fde-report`, `fde-triage` |
|
|
21
|
-
| `full` | standard + `build-state` | standard + `state` |
|
|
20
|
+
| `standard` *(default)* | core + `bootstrap-project`, `refresh-project`, `lint-state`, `fde-intake`, `fde-report`, `fde-triage` + FDE reference pack | core + `bootstrap`, `refresh`, `lint`, `fde-intake`, `fde-report`, `fde-triage` |
|
|
21
|
+
| `full` | standard + `build-state`, `link-entities`, `dashboard`, `tour` | standard + `state`, `link-entities`, `dashboard`, `tour` |
|
|
22
22
|
| **`preview`** *(opt-in)* | standard + `propose-ado-update`, `apply-ado-update` | standard + `propose-ado`, `apply-ado` |
|
|
23
23
|
|
|
24
24
|
The Evidence/ folder produced by `aggregate` is the **public contract** between Kushi and any downstream consumer (external rollup repo, BI tooling). See `docs/reference/evidence-contract.md`.
|
|
@@ -39,6 +39,7 @@ The Evidence/ folder produced by `aggregate` is the **public contract** between
|
|
|
39
39
|
| `@Kushi link-entities <project>` | standard+ | n/a (read-only) | v5.0.0 — `link-entities` writes `<project>/Evidence/_graph/project-graph.json` from per-source `_index/entities.yml` + weekly CSC bodies. Deterministic by default; opt-in LLM augment via `m365Mutable.graph.llm_infer`. |
|
|
40
40
|
| `@Kushi dashboard <project>` | standard+ | n/a (read-only) | v5.0.0 — `dashboard` writes `<project>/dashboard.html` (single self-contained file). Cytoscape.js v3 + Clawpilot theme. |
|
|
41
41
|
| `@Kushi tour <project> [--top N]` | standard+ | n/a (read-only) | v5.0.0 — `tour` writes `<project>/State/tour.md`, an auto-generated week-in-review walkthrough scored by `recency_weight × cross_ref_count` (14-day half-life). Default N=10. |
|
|
42
|
+
| `@Kushi lint <project>` | standard+ | n/a (read-only) | v5.1.0 — `lint-state` runs wiki-lint checks against `State/` (contradictions, stale claims, orphans, missing cross-refs, data gaps). Writes `State/reports/lint-YYYY-MM-DD.md`. |
|
|
42
43
|
| `@Kushi consolidate <project> last N days` | core+ | N days | `consolidate-evidence` only |
|
|
43
44
|
| `@Kushi status <project>` | core+ | n/a | `project-status` — show run-log |
|
|
44
45
|
| `@Kushi ask <project> <question>` | core+ | n/a (read-only) | `ask-project` — cited Q&A over Evidence/ (+ State/ on full) |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "living-wiki"
|
|
3
|
+
description: "v5.1.0 — Incremental-maintenance + contradiction-handling pattern for State/. Build-state preserves human edits outside <!-- kushi:auto --> fences; contradictions are flagged with callouts, never silently overwritten. Adapted from Karpathy's living-wiki gist. Authored to agentskills.io spec; deltas: adds incremental fencing convention + contradiction lifecycle + auto-resolve threshold not present in source."
|
|
4
|
+
applies_to: "build-state, lint-state, all writers touching State/"
|
|
5
|
+
since: "kushi v5.1.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# living-wiki — doctrine
|
|
9
|
+
|
|
10
|
+
> **Authored to [agentskills.io](https://agentskills.io/skill-creation/best-practices) spec.**
|
|
11
|
+
> Deltas from source (Karpathy's living-wiki gist): adds `<!-- kushi:auto -->` fencing convention, contradiction lifecycle (current → contradicted → superseded), auto-resolve threshold with 3 conditions, `_review-queue.md` open-contradiction tracker.
|
|
12
|
+
|
|
13
|
+
Adapted from [Karpathy's living-wiki gist](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f). The core insight: a wiki maintained by both humans and machines must have clear ownership boundaries and explicit conflict resolution — silent overwrites destroy trust.
|
|
14
|
+
|
|
15
|
+
## Rules (HARD)
|
|
16
|
+
|
|
17
|
+
### 1. Incremental maintenance
|
|
18
|
+
|
|
19
|
+
Every writer skill that touches `State/` MUST:
|
|
20
|
+
|
|
21
|
+
- **Preserve human edits.** Content outside `<!-- kushi:auto:start -->` / `<!-- kushi:auto:end -->` fences is NEVER modified by automation. Writers only update derived sections between those fences.
|
|
22
|
+
- **Never wipe the page.** If a page exists, read it first. Regenerate only the fenced regions. If the page does not exist, create it with fences around any auto-derived content.
|
|
23
|
+
- **Fence hygiene.** Every auto-derived block MUST be wrapped:
|
|
24
|
+
```markdown
|
|
25
|
+
<!-- kushi:auto:start section="<section-id>" -->
|
|
26
|
+
...auto-derived content...
|
|
27
|
+
<!-- kushi:auto:end section="<section-id>" -->
|
|
28
|
+
```
|
|
29
|
+
The `section` attribute identifies which derivation produced this block (e.g., `section="entity-summary"`, `section="related-entities"`).
|
|
30
|
+
|
|
31
|
+
### 2. Contradiction handling
|
|
32
|
+
|
|
33
|
+
When a new fact contradicts an existing claim (different value for the same entity property):
|
|
34
|
+
|
|
35
|
+
- **Flag both claims** with Obsidian-compatible callouts:
|
|
36
|
+
```markdown
|
|
37
|
+
> [!warning] Contradicted by build-state run 2026-05-28
|
|
38
|
+
> Previous value: "MACC $500K" [source: ushak/crm/weekly/2026-05-01_crm-csc.md#acme-opp · 2026-05-01]
|
|
39
|
+
|
|
40
|
+
> [!info] New value (2026-05-28)
|
|
41
|
+
> Current value: "MACC $750K" [source: ushak/crm/weekly/2026-05-22_crm-csc.md#acme-opp · 2026-05-22]
|
|
42
|
+
```
|
|
43
|
+
- **Never silently overwrite.** The old value stays visible until explicitly resolved.
|
|
44
|
+
- **Add to `_review-queue.md`** with entity, property, old value, new value, both sources, and date flagged.
|
|
45
|
+
|
|
46
|
+
### 3. Contradiction lifecycle
|
|
47
|
+
|
|
48
|
+
Each contradicted claim moves through states:
|
|
49
|
+
|
|
50
|
+
| State | Meaning |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `current` | Active, accepted truth. |
|
|
53
|
+
| `contradicted` | A newer claim disagrees. Both are visible. |
|
|
54
|
+
| `superseded` | Resolved — old claim is archived. The newer (or human-chosen) value wins. |
|
|
55
|
+
|
|
56
|
+
### 4. Auto-resolve threshold
|
|
57
|
+
|
|
58
|
+
A contradicted claim MAY be automatically marked `superseded` (and removed from `_review-queue.md`) when ALL of:
|
|
59
|
+
|
|
60
|
+
1. The new claim has **≥ 3 corroborating sources from different surfaces** (e.g., meetings + CRM + email all agree on the new value).
|
|
61
|
+
2. The contradicted claim is **≥ 30 days old** (based on its source citation date).
|
|
62
|
+
3. The contradicted claim has **no human override marker** (`<!-- kushi:human-override -->` above the claim blocks auto-resolve).
|
|
63
|
+
|
|
64
|
+
When auto-resolved, replace the callout pair with:
|
|
65
|
+
```markdown
|
|
66
|
+
> [!info] Auto-resolved 2026-06-28 — superseded by 3+ corroborating sources (meetings, crm, email)
|
|
67
|
+
> Previous value archived: "MACC $500K" [source: ...]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 5. `_review-queue.md`
|
|
71
|
+
|
|
72
|
+
Located at `Evidence/<alias>/State/_review-queue.md`. Lists all open (unresolved) contradictions:
|
|
73
|
+
|
|
74
|
+
```markdown
|
|
75
|
+
# Review Queue
|
|
76
|
+
|
|
77
|
+
| Entity | Property | Old value | New value | Flagged | Sources |
|
|
78
|
+
|---|---|---|---|---|---|
|
|
79
|
+
| Acme Opp | MACC | $500K | $750K | 2026-05-28 | crm, meetings |
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Updated by `build-state` on every run. Cleared when contradictions are resolved (auto or manual).
|
|
83
|
+
|
|
84
|
+
## References
|
|
85
|
+
|
|
86
|
+
- [Karpathy's living-wiki gist](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)
|
|
87
|
+
- `karpathy-state-layout.instructions.md` (v5.0.0 base layout)
|
|
88
|
+
- `wiki-lint.instructions.md` (contradiction-flagged finding class)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "log-format"
|
|
3
|
+
description: "v5.1.0 — Canonical log format for State/log.md. Every writer appends reverse-chronological entries with grep-friendly headings. Adapted from axoviq-ai/synthadoc log-as-history pattern. Authored to agentskills.io spec; deltas: kushi-specific ops taxonomy, index.md 'Last touched' pointer, append-only rule."
|
|
4
|
+
applies_to: "every writer skill that touches State/ (bootstrap-project, refresh-project, build-state, lint-state, link-entities, dashboard, tour, ask-project --file-back)"
|
|
5
|
+
since: "kushi v5.1.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# log-format — doctrine
|
|
9
|
+
|
|
10
|
+
> **Authored to [agentskills.io](https://agentskills.io/skill-creation/best-practices) spec.**
|
|
11
|
+
> Deltas from source (axoviq-ai/synthadoc): kushi-specific op taxonomy (`bootstrap`, `refresh`, `build-state`, `lint-state`, `ask-fileback`, `link-entities`, `dashboard`, `tour`), `index.md` "Last touched" pointer, reverse-chronological append-only rule.
|
|
12
|
+
|
|
13
|
+
Adapted from [axoviq-ai/synthadoc](https://github.com/axoviq-ai/synthadoc) — the log-as-grep-friendly-history pattern.
|
|
14
|
+
|
|
15
|
+
## Rules (HARD)
|
|
16
|
+
|
|
17
|
+
### 1. Canonical format
|
|
18
|
+
|
|
19
|
+
Every entry in `Evidence/<alias>/State/log.md` MUST use this heading format:
|
|
20
|
+
|
|
21
|
+
```markdown
|
|
22
|
+
## [YYYY-MM-DD HH:MM] <op> | <title>
|
|
23
|
+
|
|
24
|
+
<1–3 line summary>
|
|
25
|
+
Sources: <comma-separated source pointers>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
```markdown
|
|
30
|
+
## [2026-05-28 14:30] build-state | Incremental rebuild (3 entities updated)
|
|
31
|
+
|
|
32
|
+
Re-derived people/jane-doe.md, decisions/macc-increase.md, risks/timeline-slip.md.
|
|
33
|
+
Contradictions flagged: 1 (Acme Opp MACC). Auto-resolved: 0.
|
|
34
|
+
Sources: ushak/crm/weekly/2026-05-22_crm-csc.md, ushak/meetings/weekly/2026-05-21_meetings-csc.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Grep-friendly
|
|
38
|
+
|
|
39
|
+
The heading format is designed for grep:
|
|
40
|
+
```bash
|
|
41
|
+
grep '^## \[2026-05-' log.md # all May 2026 entries
|
|
42
|
+
grep '^## \[.*\] build-state' log.md # all build-state runs
|
|
43
|
+
grep '^## \[.*\] lint-state' log.md # all lint runs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Ops taxonomy (closed set)
|
|
47
|
+
|
|
48
|
+
| Op | Writer skill | Meaning |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `bootstrap` | bootstrap-project | First-time project setup |
|
|
51
|
+
| `refresh` | refresh-project | Incremental evidence pull + state rebuild |
|
|
52
|
+
| `build-state` | build-state | Pure re-render of State/ from Evidence/ |
|
|
53
|
+
| `lint-state` | lint-state | Wiki lint pass over State/ |
|
|
54
|
+
| `ask-fileback` | ask-project --file-back | Q&A answer written back to State/answers/ |
|
|
55
|
+
| `link-entities` | link-entities | Cross-source graph rebuild |
|
|
56
|
+
| `dashboard` | dashboard | Dashboard HTML regeneration |
|
|
57
|
+
| `tour` | tour | Guided tour regeneration |
|
|
58
|
+
|
|
59
|
+
### 4. Append-only, reverse-chronological
|
|
60
|
+
|
|
61
|
+
- New entries are **prepended** (newest at top, after the front-matter and file header).
|
|
62
|
+
- Entries are NEVER deleted or rewritten.
|
|
63
|
+
- Readers scan top-down for recency.
|
|
64
|
+
|
|
65
|
+
### 5. Index.md "Last touched" pointer
|
|
66
|
+
|
|
67
|
+
After appending to `log.md`, the writer MUST also update the top of `State/index.md`:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
> Last touched: YYYY-MM-DD HH:MM by <op> ([log](log.md))
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This single-line pointer lives immediately after the front-matter block of `index.md`.
|
|
74
|
+
|
|
75
|
+
## References
|
|
76
|
+
|
|
77
|
+
- [axoviq-ai/synthadoc](https://github.com/axoviq-ai/synthadoc)
|
|
78
|
+
- `karpathy-state-layout.instructions.md` (log.md base contract)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "wiki-lint"
|
|
3
|
+
description: "v5.1.0 — Wiki lint finding classes for State/. Detects contradictions, stale claims, orphan pages, missing cross-refs, and data gaps. Each finding includes a ready-to-paste fix snippet. Adapted from sametbrr/llm-wiki-manager lint patterns. Authored to agentskills.io spec; deltas: kushi-specific finding classes (contradiction-flagged, stale-claim, orphan-page, missing-cross-ref, data-gap), fix-snippet convention."
|
|
4
|
+
applies_to: "lint-state skill"
|
|
5
|
+
since: "kushi v5.1.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# wiki-lint — doctrine
|
|
9
|
+
|
|
10
|
+
> **Authored to [agentskills.io](https://agentskills.io/skill-creation/best-practices) spec.**
|
|
11
|
+
> Deltas from source (sametbrr/llm-wiki-manager): kushi-specific finding classes mapped to State/ layout, Obsidian-callout aware, fix-snippet per finding (not just a diagnostic), integration with `_review-queue.md`.
|
|
12
|
+
|
|
13
|
+
Adapted from [sametbrr/llm-wiki-manager](https://github.com/sametbrr/llm-wiki-manager) — wiki lint patterns.
|
|
14
|
+
|
|
15
|
+
## Finding classes
|
|
16
|
+
|
|
17
|
+
### 1. `contradiction-flagged`
|
|
18
|
+
|
|
19
|
+
**What:** Count unresolved `> [!warning] Contradicted` callouts in State/ pages.
|
|
20
|
+
|
|
21
|
+
**Detection:** Regex `^> \[!warning\] Contradicted` in any `State/**/*.md`.
|
|
22
|
+
|
|
23
|
+
**Severity:** warning (informational — contradictions are expected during active projects).
|
|
24
|
+
|
|
25
|
+
**Fix snippet:**
|
|
26
|
+
```markdown
|
|
27
|
+
<!-- To resolve: review both values, pick the correct one, then either:
|
|
28
|
+
(a) Delete the callout pair and keep the winning value, OR
|
|
29
|
+
(b) Add <!-- kushi:human-override --> above the old value to prevent auto-resolve. -->
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. `stale-claim`
|
|
33
|
+
|
|
34
|
+
**What:** A claim in a State page is ≥ 60 days old (based on its `[source: ... · YYYY-MM-DD]` citation date) with no corroborating refresh since.
|
|
35
|
+
|
|
36
|
+
**Detection:** Parse inline citations `[source: ... · YYYY-MM-DD]`; flag if the newest citation for an entity property is > 60 days old AND no `build-state` or `refresh` log entry touched that entity since.
|
|
37
|
+
|
|
38
|
+
**Severity:** warning.
|
|
39
|
+
|
|
40
|
+
**Fix snippet:**
|
|
41
|
+
```markdown
|
|
42
|
+
<!-- Stale claim (>60 days). Run '@Kushi refresh <project>' to pull fresh evidence,
|
|
43
|
+
then '@Kushi state <project>' to rebuild. If the claim is still valid, add a
|
|
44
|
+
corroborating citation from a recent source. -->
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 3. `orphan-page`
|
|
48
|
+
|
|
49
|
+
**What:** A page in `State/` (with `kushi_state_page: true`) that is NOT linked from `index.md` or any other State page.
|
|
50
|
+
|
|
51
|
+
**Detection:** Enumerate all `State/**/*.md` with `kushi_state_page: true`. For each, check if its path or `[[wikilink]]` slug appears in `index.md` or any sibling page body.
|
|
52
|
+
|
|
53
|
+
**Severity:** warning.
|
|
54
|
+
|
|
55
|
+
**Fix snippet:**
|
|
56
|
+
```markdown
|
|
57
|
+
<!-- Orphan page: not linked from index.md or any other State page.
|
|
58
|
+
Fix: add a [[category/slug]] link to index.md under the correct category heading,
|
|
59
|
+
OR delete this page if it was generated in error. -->
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. `missing-cross-ref`
|
|
63
|
+
|
|
64
|
+
**What:** An entity mentioned in a page body (matching a known entity name from `Evidence/_graph/project-graph.json` nodes) but NOT listed in the page's `entity_ids` or `related` frontmatter arrays.
|
|
65
|
+
|
|
66
|
+
**Detection:** Load entity names from graph nodes. For each State page, scan body for entity-name matches not in frontmatter.
|
|
67
|
+
|
|
68
|
+
**Severity:** info.
|
|
69
|
+
|
|
70
|
+
**Fix snippet:**
|
|
71
|
+
```markdown
|
|
72
|
+
<!-- Missing cross-ref: entity "<name>" mentioned in body but not in frontmatter.
|
|
73
|
+
Fix: add to `related:` array in front-matter:
|
|
74
|
+
related:
|
|
75
|
+
- category/entity-slug -->
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 5. `data-gap`
|
|
79
|
+
|
|
80
|
+
**What:** A required section header is present in a State page but its body (content between that header and the next header or EOF) is empty or contains only whitespace/placeholder text.
|
|
81
|
+
|
|
82
|
+
**Detection:** For each State page, parse `###` and `##` sections. Flag sections whose body is empty, contains only `<!-- TODO -->`, or is fewer than 2 non-blank lines.
|
|
83
|
+
|
|
84
|
+
**Severity:** info.
|
|
85
|
+
|
|
86
|
+
**Fix snippet:**
|
|
87
|
+
```markdown
|
|
88
|
+
<!-- Data gap: section "<heading>" has no content.
|
|
89
|
+
Fix: run '@Kushi refresh <project>' to pull evidence that populates this section,
|
|
90
|
+
or remove the section header if it's not applicable to this entity. -->
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Output format
|
|
94
|
+
|
|
95
|
+
Each finding in the lint report:
|
|
96
|
+
|
|
97
|
+
```markdown
|
|
98
|
+
### <class>: <entity/page> — <short description>
|
|
99
|
+
|
|
100
|
+
**File:** `State/<path>`
|
|
101
|
+
**Line:** <N>
|
|
102
|
+
**Fix:**
|
|
103
|
+
<fix snippet>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## References
|
|
107
|
+
|
|
108
|
+
- [sametbrr/llm-wiki-manager](https://github.com/sametbrr/llm-wiki-manager)
|
|
109
|
+
- `living-wiki.instructions.md` (contradiction lifecycle)
|
|
110
|
+
- `karpathy-state-layout.instructions.md` (page convention)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Appends a canonical entry to State/log.md per log-format.instructions.md.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Idempotent helper called by every writer skill after completing a State/ write.
|
|
7
|
+
Prepends (reverse-chronological) a new entry after the file header.
|
|
8
|
+
|
|
9
|
+
.PARAMETER StateDir
|
|
10
|
+
Path to the State/ directory (e.g., Evidence/<alias>/State/).
|
|
11
|
+
|
|
12
|
+
.PARAMETER Op
|
|
13
|
+
Operation name from the closed taxonomy: bootstrap, refresh, build-state, lint-state,
|
|
14
|
+
ask-fileback, link-entities, dashboard, tour.
|
|
15
|
+
|
|
16
|
+
.PARAMETER Title
|
|
17
|
+
Short one-line title for the log entry.
|
|
18
|
+
|
|
19
|
+
.PARAMETER Summary
|
|
20
|
+
1–3 line summary body.
|
|
21
|
+
|
|
22
|
+
.PARAMETER Sources
|
|
23
|
+
Comma-separated source pointers.
|
|
24
|
+
#>
|
|
25
|
+
[CmdletBinding()]
|
|
26
|
+
param(
|
|
27
|
+
[Parameter(Mandatory)][string]$StateDir,
|
|
28
|
+
[Parameter(Mandatory)][ValidateSet('bootstrap','refresh','build-state','lint-state','ask-fileback','link-entities','dashboard','tour')][string]$Op,
|
|
29
|
+
[Parameter(Mandatory)][string]$Title,
|
|
30
|
+
[string]$Summary = '',
|
|
31
|
+
[string]$Sources = ''
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
$ErrorActionPreference = 'Stop'
|
|
35
|
+
|
|
36
|
+
$logFile = Join-Path $StateDir 'log.md'
|
|
37
|
+
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm'
|
|
38
|
+
$entry = "## [$timestamp] $Op | $Title`n"
|
|
39
|
+
if ($Summary) { $entry += "`n$Summary" }
|
|
40
|
+
if ($Sources) { $entry += "`nSources: $Sources" }
|
|
41
|
+
$entry += "`n"
|
|
42
|
+
|
|
43
|
+
if (-not (Test-Path $logFile)) {
|
|
44
|
+
# Create with header
|
|
45
|
+
$header = @"
|
|
46
|
+
---
|
|
47
|
+
kushi_state_log: true
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
# State Log
|
|
51
|
+
|
|
52
|
+
$entry
|
|
53
|
+
"@
|
|
54
|
+
New-Item -Path $logFile -ItemType File -Force | Out-Null
|
|
55
|
+
Set-Content -Path $logFile -Value $header -Encoding utf8NoBOM
|
|
56
|
+
} else {
|
|
57
|
+
$content = Get-Content -Raw $logFile
|
|
58
|
+
# Insert after the header block (after "# State Log" line or after front-matter)
|
|
59
|
+
if ($content -match '(?ms)(^---\r?\n.*?\r?\n---\r?\n\r?\n# [^\r\n]+\r?\n)(.*)$') {
|
|
60
|
+
$headerPart = $Matches[1]
|
|
61
|
+
$bodyPart = $Matches[2]
|
|
62
|
+
$newContent = $headerPart + "`n" + $entry + "`n" + $bodyPart
|
|
63
|
+
} elseif ($content -match '(?ms)(^# [^\r\n]+\r?\n)(.*)$') {
|
|
64
|
+
$headerPart = $Matches[1]
|
|
65
|
+
$bodyPart = $Matches[2]
|
|
66
|
+
$newContent = $headerPart + "`n" + $entry + "`n" + $bodyPart
|
|
67
|
+
} else {
|
|
68
|
+
$newContent = $entry + "`n" + $content
|
|
69
|
+
}
|
|
70
|
+
Set-Content -Path $logFile -Value $newContent -Encoding utf8NoBOM
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Write-Verbose "Appended log entry: [$timestamp] $Op | $Title"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Updates the "Last touched" pointer at the top of State/index.md per log-format.instructions.md.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Idempotent helper. Updates or inserts the "Last touched" line immediately after
|
|
7
|
+
the front-matter block in index.md.
|
|
8
|
+
|
|
9
|
+
.PARAMETER StateDir
|
|
10
|
+
Path to the State/ directory.
|
|
11
|
+
|
|
12
|
+
.PARAMETER Op
|
|
13
|
+
Operation name (same taxonomy as Append-StateLog).
|
|
14
|
+
#>
|
|
15
|
+
[CmdletBinding()]
|
|
16
|
+
param(
|
|
17
|
+
[Parameter(Mandatory)][string]$StateDir,
|
|
18
|
+
[Parameter(Mandatory)][string]$Op
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
$ErrorActionPreference = 'Stop'
|
|
22
|
+
|
|
23
|
+
$indexFile = Join-Path $StateDir 'index.md'
|
|
24
|
+
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm'
|
|
25
|
+
$pointer = "> Last touched: $timestamp by $Op ([log](log.md))"
|
|
26
|
+
|
|
27
|
+
if (-not (Test-Path $indexFile)) {
|
|
28
|
+
Write-Warning "index.md not found at $indexFile — skipping Update-StateIndex."
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
$content = Get-Content -Raw $indexFile
|
|
33
|
+
$pointerPattern = '(?m)^> Last touched:.*$'
|
|
34
|
+
|
|
35
|
+
if ($content -match $pointerPattern) {
|
|
36
|
+
$content = $content -replace $pointerPattern, $pointer
|
|
37
|
+
} else {
|
|
38
|
+
# Insert after front-matter closing ---
|
|
39
|
+
if ($content -match '(?ms)(^---\r?\n.*?\r?\n---\r?\n)(.*)$') {
|
|
40
|
+
$content = $Matches[1] + "`n" + $pointer + "`n" + $Matches[2]
|
|
41
|
+
} else {
|
|
42
|
+
$content = $pointer + "`n`n" + $content
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Set-Content -Path $indexFile -Value $content -Encoding utf8NoBOM
|
|
47
|
+
Write-Verbose "Updated index.md: Last touched $timestamp by $Op"
|
|
@@ -42,6 +42,36 @@ The user does NOT need to type `/ask-project` or `@Kushi ask`. If the message:
|
|
|
42
42
|
- **Freshness gate, not freshness auto-fix.** If the freshest source relevant to the question is older than `chat.freshness_warn_days` (default 14), warn the user and offer `@Kushi refresh <project>` — but NEVER auto-refresh.
|
|
43
43
|
- **Reference packs may be consulted for domain doctrine.** When the question maps to a known reference-pack domain (currently only `fde/` — FDE stages, fitness, CRM status meanings, intake gates, risk categories, "MACC", "is this FDE-fit"), ALSO load the matching reference pack as additional grounding using the 3-layer override order (project → user → packaged). Cite reference-pack assertions with `[source: reference-packs/<pack>/<file>.md · <layer>]` where layer is `packaged` / `user-override` / `project-override`. Reference-pack content NEVER overrides project Evidence/ for facts about *this* project; it only provides definitions, gates, and rubrics.
|
|
44
44
|
|
|
45
|
+
## --file-back option (v5.1.0+)
|
|
46
|
+
|
|
47
|
+
When the user passes `--file-back` (or says "file this answer back", "save this answer"):
|
|
48
|
+
|
|
49
|
+
1. After answering normally, write the Q+A to `Evidence/<alias>/State/answers/YYYY-MM-DD_<slug>.md`:
|
|
50
|
+
```markdown
|
|
51
|
+
---
|
|
52
|
+
question: "<the user's original question>"
|
|
53
|
+
asked_at: <ISO-8601 timestamp>
|
|
54
|
+
sources: [<list of cited source paths>]
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Answer
|
|
58
|
+
|
|
59
|
+
<the answer text, with citations preserved>
|
|
60
|
+
|
|
61
|
+
## Sources
|
|
62
|
+
|
|
63
|
+
- <source 1>
|
|
64
|
+
- <source 2>
|
|
65
|
+
```
|
|
66
|
+
2. The `<slug>` is derived from the question: lowercase, alphanumeric + hyphens, ≤ 60 chars.
|
|
67
|
+
3. Append `ask-fileback` entry to `State/log.md` via `Append-StateLog.ps1`:
|
|
68
|
+
- Title: `Filed answer: <slug>`
|
|
69
|
+
- Summary: one-line description of the question.
|
|
70
|
+
4. Update `State/index.md` via `Update-StateIndex.ps1 -Op ask-fileback`.
|
|
71
|
+
5. If `State/answers/` does not exist, create it.
|
|
72
|
+
|
|
73
|
+
The `--file-back` flag is OPTIONAL. Without it, ask-project behaves exactly as before (read-only, no writes).
|
|
74
|
+
|
|
45
75
|
## Inputs
|
|
46
76
|
|
|
47
77
|
- `<project>` — fuzzy-matched project name. If multiple plausible matches, ask the user.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "build-state"
|
|
3
|
-
version: "
|
|
3
|
+
version: "5.0.0"
|
|
4
4
|
description: "USE WHEN the user says \"regenerate State for <X>\", \"rebuild State\", or \"@Kushi state <X>\" AND the project already has Evidence/ populated. DO NOT USE to pull new evidence (use refresh-project or pull-*). Capability: pure re-render — reads Evidence/_index/entities.yml + weekly CSC + legacy fallback, writes <project>/State/ in BOTH legacy 00–09 synthesis (full profile) AND Karpathy layout (index.md + log.md + per-entity pages + CLAUDE.md/AGENTS.md). Plan-validate-execute writer."
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -56,7 +56,23 @@ runs — transient working artifact, not authoritative truth.
|
|
|
56
56
|
|
|
57
57
|
## Steps
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
## v5.1.0 — Incremental mode (HARD RULE; per `living-wiki.instructions.md`)
|
|
61
|
+
|
|
62
|
+
Build-state is now incremental by default:
|
|
63
|
+
|
|
64
|
+
1. **Read existing State/ pages.** Before regenerating, load every existing `State/**/*.md`.
|
|
65
|
+
2. **Preserve human edits.** Content OUTSIDE `<!-- kushi:auto:start -->` / `<!-- kushi:auto:end -->` fences is NEVER modified.
|
|
66
|
+
3. **Re-derive fenced regions only.** For each `<!-- kushi:auto:start section="<id>" -->` block, re-derive content from current evidence and replace the block contents.
|
|
67
|
+
4. **Contradiction detection.** When a re-derived fact contradicts an existing fact (different value for same entity property):
|
|
68
|
+
- Wrap both in `> [!warning] Contradicted` / `> [!info] New value` callouts per `living-wiki.instructions.md`.
|
|
69
|
+
- Add entity to `_review-queue.md`.
|
|
70
|
+
5. **Auto-resolve.** Apply the auto-resolve threshold from `living-wiki.instructions.md` (≥3 sources + ≥30 days old + no human override).
|
|
71
|
+
6. **Log + index.** Append `build-state` entry to `State/log.md` via `Append-StateLog.ps1`. Update `State/index.md` via `Update-StateIndex.ps1`.
|
|
72
|
+
|
|
73
|
+
When creating NEW pages (entity not previously in State/), wrap all auto-derived content in `<!-- kushi:auto -->` fences.
|
|
74
|
+
|
|
75
|
+
|
|
60
76
|
## Step checklist
|
|
61
77
|
|
|
62
78
|
Progress-trackable view of the steps below. Each `### Step` block expands the corresponding checkbox.
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "lint-state"
|
|
3
|
+
version: "1.0.0"
|
|
4
|
+
description: "USE WHEN the user says 'lint state', 'check state health', 'wiki lint', 'kushi lint <project>', or 'find stale/orphan/contradictions in State'. DO NOT USE for evidence-level validation (use self-check) or for rebuilding State (use build-state). Capability: runs wiki-lint finding classes against Evidence/<alias>/State/, writes dated lint report, updates log.md + index.md."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill: lint-state
|
|
8
|
+
|
|
9
|
+
Run wiki-lint checks against a project's `State/` directory per `wiki-lint.instructions.md`. Reports contradictions, stale claims, orphan pages, missing cross-refs, and data gaps — each with a ready-to-paste fix snippet.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- "lint state for `<project>`"
|
|
14
|
+
- "check state health"
|
|
15
|
+
- "wiki lint `<project>`"
|
|
16
|
+
- `kushi lint <project>` (CLI verb)
|
|
17
|
+
- "find contradictions in State"
|
|
18
|
+
- "any stale claims in `<project>`?"
|
|
19
|
+
|
|
20
|
+
## Inputs
|
|
21
|
+
|
|
22
|
+
- `<project>` — engagement name (fuzzy-matched per `engagement-root-resolution.instructions.md`).
|
|
23
|
+
|
|
24
|
+
## Step checklist
|
|
25
|
+
|
|
26
|
+
- [ ] Step 1 — Resolve project root + State/ path
|
|
27
|
+
- [ ] Step 2 — Run finding-class checks
|
|
28
|
+
- [ ] Step 3 — Generate lint report
|
|
29
|
+
- [ ] Step 4 — Update log.md + index.md
|
|
30
|
+
- [ ] Step 5 — Print summary
|
|
31
|
+
|
|
32
|
+
### Step 1 — Resolve project root
|
|
33
|
+
|
|
34
|
+
Resolve `<engagement-root>/<project>/Evidence/<alias>/State/` using standard engagement-root resolution. Verify `State/` exists; if not, advise running `build-state` first.
|
|
35
|
+
|
|
36
|
+
### Step 2 — Run finding-class checks
|
|
37
|
+
|
|
38
|
+
Per `wiki-lint.instructions.md`, execute each finding class:
|
|
39
|
+
|
|
40
|
+
1. **contradiction-flagged**: scan `State/**/*.md` for `> [!warning] Contradicted` callouts.
|
|
41
|
+
2. **stale-claim**: parse citations, flag entities with newest citation > 60 days old.
|
|
42
|
+
3. **orphan-page**: find pages with `kushi_state_page: true` not linked from index.md or siblings.
|
|
43
|
+
4. **missing-cross-ref**: cross-reference entity mentions against frontmatter `entity_ids`/`related`.
|
|
44
|
+
5. **data-gap**: find section headers with empty/placeholder bodies.
|
|
45
|
+
|
|
46
|
+
### Step 3 — Generate lint report
|
|
47
|
+
|
|
48
|
+
Write `Evidence/<alias>/State/reports/lint-YYYY-MM-DD.md`:
|
|
49
|
+
|
|
50
|
+
```markdown
|
|
51
|
+
---
|
|
52
|
+
generated_at: "YYYY-MM-DDTHH:MM:SSZ"
|
|
53
|
+
generated_by: "lint-state v1.0.0"
|
|
54
|
+
findings_count: N
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# Lint Report — YYYY-MM-DD
|
|
58
|
+
|
|
59
|
+
## Summary
|
|
60
|
+
|
|
61
|
+
| Class | Count | Severity |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| contradiction-flagged | N | warning |
|
|
64
|
+
| stale-claim | N | warning |
|
|
65
|
+
| orphan-page | N | warning |
|
|
66
|
+
| missing-cross-ref | N | info |
|
|
67
|
+
| data-gap | N | info |
|
|
68
|
+
|
|
69
|
+
**Total findings: N**
|
|
70
|
+
|
|
71
|
+
## Findings
|
|
72
|
+
|
|
73
|
+
### contradiction-flagged: <entity> — <description>
|
|
74
|
+
...per wiki-lint.instructions.md format...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Step 4 — Update log.md + index.md
|
|
78
|
+
|
|
79
|
+
Call shared helpers:
|
|
80
|
+
- `Append-StateLog.ps1 -Op lint-state -Title "Lint pass (<N> findings)"`
|
|
81
|
+
- `Update-StateIndex.ps1 -Op lint-state`
|
|
82
|
+
|
|
83
|
+
### Step 5 — Print summary
|
|
84
|
+
|
|
85
|
+
One-line console output: `lint-state: <N> findings (<breakdown by class>). Report: State/reports/lint-YYYY-MM-DD.md`
|
|
86
|
+
|
|
87
|
+
## Validation loop
|
|
88
|
+
|
|
89
|
+
After writing outputs:
|
|
90
|
+
1. Verify `State/reports/lint-YYYY-MM-DD.md` exists and has valid frontmatter.
|
|
91
|
+
2. Verify `State/log.md` has the new entry at top.
|
|
92
|
+
3. Verify `State/index.md` "Last touched" pointer is updated.
|
|
93
|
+
|
|
94
|
+
## References
|
|
95
|
+
|
|
96
|
+
- `../../instructions/wiki-lint.instructions.md`
|
|
97
|
+
- `../../instructions/log-format.instructions.md`
|
|
98
|
+
- `../../instructions/living-wiki.instructions.md`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill": "lint-state",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Wiki-lint checks against State/ — contradictions, stale claims, orphans, cross-refs, data gaps.",
|
|
5
|
+
"cases": [
|
|
6
|
+
{
|
|
7
|
+
"id": "lint-state-clean",
|
|
8
|
+
"name": "Lint a clean State/ with no findings",
|
|
9
|
+
"input": "Run lint against a well-maintained State/ directory with no contradictions, stale claims, or orphans.",
|
|
10
|
+
"expected_assertions": [
|
|
11
|
+
{ "type": "regex-match", "target": "stdout", "pattern": "lint-state: 0 findings" }
|
|
12
|
+
],
|
|
13
|
+
"grader_type": "script"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "lint-state-contradiction",
|
|
17
|
+
"name": "Lint detects contradiction callouts",
|
|
18
|
+
"input": "Run lint against a State/ directory with one unresolved > [!warning] Contradicted callout.",
|
|
19
|
+
"expected_assertions": [
|
|
20
|
+
{ "type": "regex-match", "target": "stdout", "pattern": "contradiction-flagged=1" }
|
|
21
|
+
],
|
|
22
|
+
"grader_type": "script"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "lint-state-orphan",
|
|
26
|
+
"name": "Lint detects orphan pages",
|
|
27
|
+
"input": "Run lint against a State/ directory with a page that has kushi_state_page: true but is not linked from index.md.",
|
|
28
|
+
"expected_assertions": [
|
|
29
|
+
{ "type": "regex-match", "target": "stdout", "pattern": "orphan-page=1" }
|
|
30
|
+
],
|
|
31
|
+
"grader_type": "script"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
lint-state — runs wiki-lint checks against a project's State/ directory.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Implements the finding classes defined in wiki-lint.instructions.md:
|
|
7
|
+
- contradiction-flagged
|
|
8
|
+
- stale-claim
|
|
9
|
+
- orphan-page
|
|
10
|
+
- missing-cross-ref
|
|
11
|
+
- data-gap
|
|
12
|
+
|
|
13
|
+
Writes a dated lint report to State/reports/, appends to State/log.md, updates
|
|
14
|
+
State/index.md "Last touched" pointer.
|
|
15
|
+
|
|
16
|
+
.PARAMETER StateDir
|
|
17
|
+
Path to the State/ directory to lint.
|
|
18
|
+
|
|
19
|
+
.PARAMETER GraphFile
|
|
20
|
+
Optional path to project-graph.json for missing-cross-ref checks.
|
|
21
|
+
|
|
22
|
+
.PARAMETER Quiet
|
|
23
|
+
Suppress console output (for programmatic use).
|
|
24
|
+
#>
|
|
25
|
+
[CmdletBinding()]
|
|
26
|
+
param(
|
|
27
|
+
[Parameter(Mandatory)][string]$StateDir,
|
|
28
|
+
[string]$GraphFile,
|
|
29
|
+
[switch]$Quiet
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
$ErrorActionPreference = 'Stop'
|
|
33
|
+
$findings = @()
|
|
34
|
+
|
|
35
|
+
# --- Helpers ---
|
|
36
|
+
function Add-LintFinding {
|
|
37
|
+
param([string]$Class, [string]$Severity, [string]$Entity, [string]$Description, [string]$File, [int]$Line, [string]$Fix)
|
|
38
|
+
$script:findings += [PSCustomObject]@{
|
|
39
|
+
class = $Class
|
|
40
|
+
severity = $Severity
|
|
41
|
+
entity = $Entity
|
|
42
|
+
description = $Description
|
|
43
|
+
file = $File
|
|
44
|
+
line = $Line
|
|
45
|
+
fix = $Fix
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# --- Check 1: contradiction-flagged ---
|
|
50
|
+
$stateFiles = Get-ChildItem -Path $StateDir -Recurse -Filter '*.md' -ErrorAction SilentlyContinue
|
|
51
|
+
foreach ($f in $stateFiles) {
|
|
52
|
+
$lines = Get-Content -Path $f.FullName -ErrorAction SilentlyContinue
|
|
53
|
+
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
54
|
+
if ($lines[$i] -match '^\s*>\s*\[!warning\]\s*Contradicted') {
|
|
55
|
+
$entity = $f.BaseName -replace '-', ' '
|
|
56
|
+
Add-LintFinding 'contradiction-flagged' 'warning' $entity "Unresolved contradiction callout" $f.FullName ($i + 1) "Review both values, pick the correct one. Delete callout pair and keep winner, or add <!-- kushi:human-override --> to prevent auto-resolve."
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# --- Check 2: stale-claim ---
|
|
62
|
+
$staleDays = 60
|
|
63
|
+
$now = Get-Date
|
|
64
|
+
foreach ($f in $stateFiles) {
|
|
65
|
+
$content = Get-Content -Raw $f.FullName -ErrorAction SilentlyContinue
|
|
66
|
+
if (-not $content) { continue }
|
|
67
|
+
$citations = [regex]::Matches($content, '\[source:[^\]]*\xB7\s*(\d{4}-\d{2}-\d{2})\]')
|
|
68
|
+
if ($citations.Count -eq 0) { continue }
|
|
69
|
+
$newestDate = $null
|
|
70
|
+
foreach ($m in $citations) {
|
|
71
|
+
try {
|
|
72
|
+
$d = [datetime]::ParseExact($m.Groups[1].Value, 'yyyy-MM-dd', $null)
|
|
73
|
+
if (-not $newestDate -or $d -gt $newestDate) { $newestDate = $d }
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
if ($newestDate -and ($now - $newestDate).TotalDays -gt $staleDays) {
|
|
77
|
+
$entity = $f.BaseName -replace '-', ' '
|
|
78
|
+
Add-LintFinding 'stale-claim' 'warning' $entity "Newest citation is $([math]::Round(($now - $newestDate).TotalDays)) days old (threshold: $staleDays)" $f.FullName 0 "Run '@Kushi refresh <project>' then '@Kushi state <project>' to pull fresh evidence. If claim is still valid, add a corroborating citation from a recent source."
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# --- Check 3: orphan-page ---
|
|
83
|
+
$indexFile = Join-Path $StateDir 'index.md'
|
|
84
|
+
$indexContent = ''
|
|
85
|
+
if (Test-Path $indexFile) { $indexContent = Get-Content -Raw $indexFile }
|
|
86
|
+
$allPageContent = ''
|
|
87
|
+
foreach ($f in $stateFiles) {
|
|
88
|
+
if ($f.FullName -ne (Resolve-Path $indexFile -ErrorAction SilentlyContinue)) {
|
|
89
|
+
$allPageContent += (Get-Content -Raw $f.FullName -ErrorAction SilentlyContinue) + "`n"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
foreach ($f in $stateFiles) {
|
|
93
|
+
$content = Get-Content -Raw $f.FullName -ErrorAction SilentlyContinue
|
|
94
|
+
if ($content -notmatch 'kushi_state_page:\s*true') { continue }
|
|
95
|
+
$slug = $f.BaseName
|
|
96
|
+
$relPath = $f.FullName.Replace($StateDir, '').TrimStart('\', '/') -replace '\\', '/'
|
|
97
|
+
if ($indexContent -notmatch [regex]::Escape($slug) -and $allPageContent -notmatch [regex]::Escape($slug)) {
|
|
98
|
+
Add-LintFinding 'orphan-page' 'warning' $slug "Page not linked from index.md or any sibling" $f.FullName 0 "Add [[$(Split-Path $relPath -Parent)/$slug]] to index.md under the correct category heading, or delete this page if generated in error."
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# --- Check 4: missing-cross-ref ---
|
|
103
|
+
if ($GraphFile -and (Test-Path $GraphFile)) {
|
|
104
|
+
try {
|
|
105
|
+
$graph = Get-Content -Raw $GraphFile | ConvertFrom-Json
|
|
106
|
+
$entityNames = @()
|
|
107
|
+
if ($graph.nodes) { $entityNames = $graph.nodes | ForEach-Object { $_.name } | Where-Object { $_ } }
|
|
108
|
+
foreach ($f in $stateFiles) {
|
|
109
|
+
$content = Get-Content -Raw $f.FullName -ErrorAction SilentlyContinue
|
|
110
|
+
if ($content -notmatch 'kushi_state_page:\s*true') { continue }
|
|
111
|
+
foreach ($name in $entityNames) {
|
|
112
|
+
if ($name.Length -lt 3) { continue }
|
|
113
|
+
if ($content -match [regex]::Escape($name) -and $content -notmatch "entity_ids:.*$([regex]::Escape($name))" -and $content -notmatch "related:.*$([regex]::Escape($name))") {
|
|
114
|
+
# Check frontmatter more carefully
|
|
115
|
+
if ($content -match '(?ms)^---\r?\n(.*?)\r?\n---') {
|
|
116
|
+
$fm = $Matches[1]
|
|
117
|
+
if ($fm -notmatch [regex]::Escape($name)) {
|
|
118
|
+
Add-LintFinding 'missing-cross-ref' 'info' ($f.BaseName -replace '-',' ') "Entity '$name' mentioned in body but not in frontmatter" $f.FullName 0 "Add to related: array in front-matter."
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
Write-Warning "Could not parse graph file: $_"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# --- Check 5: data-gap ---
|
|
130
|
+
foreach ($f in $stateFiles) {
|
|
131
|
+
$content = Get-Content -Raw $f.FullName -ErrorAction SilentlyContinue
|
|
132
|
+
if ($content -notmatch 'kushi_state_page:\s*true') { continue }
|
|
133
|
+
$lines = Get-Content -Path $f.FullName
|
|
134
|
+
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
135
|
+
if ($lines[$i] -match '^#{2,3}\s+(.+)$') {
|
|
136
|
+
$heading = $Matches[1]
|
|
137
|
+
# Look at content until next heading or EOF
|
|
138
|
+
$bodyLines = @()
|
|
139
|
+
for ($j = $i + 1; $j -lt $lines.Count; $j++) {
|
|
140
|
+
if ($lines[$j] -match '^#{2,3}\s+') { break }
|
|
141
|
+
if ($lines[$j].Trim() -and $lines[$j].Trim() -ne '<!-- TODO -->' -and $lines[$j].Trim() -ne '<!-- TODO(retrofit) -->') {
|
|
142
|
+
$bodyLines += $lines[$j]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if ($bodyLines.Count -lt 2 -and $heading -notmatch '^(State Log|State Index|Review Queue)') {
|
|
146
|
+
Add-LintFinding 'data-gap' 'info' ($f.BaseName -replace '-',' ') "Section '$heading' has no meaningful content" $f.FullName ($i + 1) "Run '@Kushi refresh <project>' to pull evidence for this section, or remove the header if not applicable."
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# --- Generate report ---
|
|
153
|
+
$reportsDir = Join-Path $StateDir 'reports'
|
|
154
|
+
if (-not (Test-Path $reportsDir)) { New-Item -Path $reportsDir -ItemType Directory -Force | Out-Null }
|
|
155
|
+
|
|
156
|
+
$dateStr = Get-Date -Format 'yyyy-MM-dd'
|
|
157
|
+
$reportFile = Join-Path $reportsDir "lint-$dateStr.md"
|
|
158
|
+
$timestamp = Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'
|
|
159
|
+
|
|
160
|
+
$classCounts = @{}
|
|
161
|
+
foreach ($f2 in $findings) {
|
|
162
|
+
if (-not $classCounts.ContainsKey($f2.class)) { $classCounts[$f2.class] = 0 }
|
|
163
|
+
$classCounts[$f2.class]++
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
$report = @"
|
|
167
|
+
---
|
|
168
|
+
generated_at: "$timestamp"
|
|
169
|
+
generated_by: "lint-state v1.0.0"
|
|
170
|
+
findings_count: $($findings.Count)
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
# Lint Report — $dateStr
|
|
174
|
+
|
|
175
|
+
## Summary
|
|
176
|
+
|
|
177
|
+
| Class | Count | Severity |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
"@
|
|
180
|
+
|
|
181
|
+
$classOrder = @('contradiction-flagged','stale-claim','orphan-page','missing-cross-ref','data-gap')
|
|
182
|
+
foreach ($cls in $classOrder) {
|
|
183
|
+
$count = if ($classCounts.ContainsKey($cls)) { $classCounts[$cls] } else { 0 }
|
|
184
|
+
$sev = if ($cls -match 'contradiction|stale|orphan') { 'warning' } else { 'info' }
|
|
185
|
+
$report += "| $cls | $count | $sev |`n"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
$report += "`n**Total findings: $($findings.Count)**`n`n## Findings`n"
|
|
189
|
+
|
|
190
|
+
foreach ($f2 in $findings) {
|
|
191
|
+
$report += "`n### $($f2.class): $($f2.entity) — $($f2.description)`n`n"
|
|
192
|
+
$report += "**File:** ``$($f2.file)```n"
|
|
193
|
+
if ($f2.line -gt 0) { $report += "**Line:** $($f2.line)`n" }
|
|
194
|
+
$report += "**Fix:** $($f2.fix)`n"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Set-Content -Path $reportFile -Value $report -Encoding utf8NoBOM
|
|
198
|
+
|
|
199
|
+
# --- Update log + index ---
|
|
200
|
+
$sharedDir = Join-Path $PSScriptRoot '..\_shared'
|
|
201
|
+
$appendLog = Join-Path $sharedDir 'Append-StateLog.ps1'
|
|
202
|
+
$updateIndex = Join-Path $sharedDir 'Update-StateIndex.ps1'
|
|
203
|
+
|
|
204
|
+
if (Test-Path $appendLog) {
|
|
205
|
+
& $appendLog -StateDir $StateDir -Op 'lint-state' -Title "Lint pass ($($findings.Count) findings)" -Summary "Classes: $(($classCounts.Keys | Sort-Object | ForEach-Object { "$_=$($classCounts[$_])" }) -join ', ')" -Sources "State/**/*.md"
|
|
206
|
+
}
|
|
207
|
+
if (Test-Path $updateIndex) {
|
|
208
|
+
& $updateIndex -StateDir $StateDir -Op 'lint-state'
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# --- Console output ---
|
|
212
|
+
if (-not $Quiet) {
|
|
213
|
+
$breakdown = ($classCounts.Keys | Sort-Object | ForEach-Object { "$_=$($classCounts[$_])" }) -join ', '
|
|
214
|
+
Write-Host "lint-state: $($findings.Count) findings ($breakdown). Report: $reportFile"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Return findings as output for programmatic use
|
|
218
|
+
$findings
|
|
@@ -71,6 +71,8 @@ Checks split into **core** (always run) and **deep** (opt-in).
|
|
|
71
71
|
| D32.multi-host | Multi-host install integrity | validates `src/multi-host.mjs` exports + `bin/cli.mjs` flag handling, then performs a temp-dir dry-run install for BOTH supported hosts (Clawpilot + VS Code Chat) under a fake `$HOME` in `$env:TEMP`. Asserts SKILL.md + agent file + skills/ + prompts/ + skills-metadata.json with a kushi entry are present, then asserts a clean uninstall. NEVER touches the real `~/.copilot/` or `~/.vscode/`. See `multi-host-install.instructions.md`. |
|
|
72
72
|
| D33.evals | Skill evals framework integrity | every `plugin/skills/<name>/` (except `eval`) ships `evals/evals.json` with ≥ 2 cases and ≥ 1 assertion per case; the runner (`plugin/skills/eval/run-evals.ps1`) and schema (`plugin/skills/eval/evals.schema.json`) are present; `evals/baseline.json` exists (warn-only). Six sub-checks: `D33.evals-exist`, `D33.evals-schema`, `D33.evals-min-cases`, `D33.evals-have-assertions`, `D33.eval-runner-exists`, `D33.baseline-exists`. See `skill-evals.instructions.md`. |
|
|
73
73
|
| D34.creator-conformance | skill-creator + skill-checker harness integrity (v5.0.4+) | validates `scaffold.ps1` + `check-skill.ps1` ship and are parseable; every skill carrying the `.created-by-skill-creator` marker passes `check-skill --lint` clean; `check-skill --all --retrofit --dry-run` shows no non-additive gaps; the dogfood report at `docs/audits/v5.0.4-skill-creator-dogfood.md` is fresh (≤14 days). Five sub-checks: `D34.skill-creator-exists`, `D34.skill-checker-exists`, `D34.creator-output-conforms`, `D34.retrofit-clean`, `D34.dogfood-report-fresh`. See `skill-authoring.instructions.md`. |
|
|
74
|
+
| D35.log | State log (v5.1.0+) | `State/log.md` exists, `## [` headings match canonical format, timestamps reverse-chronological. Sub-checks: `D35.log-exists`, `D35.log-format`, `D35.log-monotonic`. |
|
|
75
|
+
| D36.contradictions | Contradictions (v5.1.0+) | `> [!warning] Contradicted` callouts well-formed, `_review-queue.md` fresh, `<!-- kushi:auto -->` fences balanced. Sub-checks: `D36.callout-syntax`, `D36.review-queue-fresh`, `D36.no-silent-overwrite`. |
|
|
74
76
|
| **CSC weekly-layout checks (kushi v4.9.0)** | | gated on `Resolve-EngagementRoots` — no-ops on the kushi repo itself. |
|
|
75
77
|
| D11.csc | CSC entity coverage + depth | every `Evidence/<alias>/<source>/weekly/*-csc.md` has ≥ 1 entity heading; per-source minimum bullet count + populated-section count (meetings 25/6, email 8/4, teams 6/3, onenote 10/4, sharepoint 8/3, crm 12/5, ado 8/4). Coverage-Notes-only blocks (low-signal escape) are exempt. |
|
|
76
78
|
| D12.csc | CSC section order | every entity block's `###` section headings appear in the canonical order: Participants → Topics → Q&A → Who Said What → Decisions → Dates & Numbers → Action Items → Next Steps → Open Questions → Risks → Customer Asks → Artifacts → Coverage Notes. |
|
|
@@ -83,7 +85,7 @@ Checks split into **core** (always run) and **deep** (opt-in).
|
|
|
83
85
|
| D19.csc | CSC legacy write guard | WARN if `snapshot/` or `stream/` contains a file modified after the v4.9.0 release date (2026-05-26). Reads of legacy layouts remain supported. |
|
|
84
86
|
| **v5.0.0 cross-source / state / dashboard / tour checks** | | gated on `Resolve-EngagementRoots` — no-ops on the kushi repo itself. |
|
|
85
87
|
| D20.graph | Entity-graph well-formed | `Evidence/_graph/project-graph.json`: valid JSON, has `schema/project/generated_at/nodes/edges`, every edge `kind` is in the closed taxonomy (`references`/`decides`/`action-item-tracks`/`discusses`/`produced-by`/`follow-up-of`/`same-thread`/`participant-of`), every edge endpoint resolves to a node. See `entity-graph.instructions.md`. |
|
|
86
|
-
| D21.state | Karpathy State layout |
|
|
88
|
+
| D21.state | Karpathy State layout | `kushi_state_page: true` pages require sibling `index.md`, `log.md`, `CLAUDE.md`, `AGENTS.md`; pages live under closed-set category folders. See `karpathy-state-layout.instructions.md`. |
|
|
87
89
|
| D22.dashboard | Dashboard produced + substituted | when `project-graph.json` exists, `<project>/dashboard.html` MUST exist, MUST NOT still contain the literal `__GRAPH_JSON__` placeholder (would indicate embedder failure), and MUST reference Cytoscape. See `dashboard-artifact.instructions.md`. |
|
|
88
90
|
| D23.tour | Tour citations resolve | every `- **Cite:** [`<path>`]` row in `<project>/State/tour.md` MUST resolve to a real file under `<project>/Evidence/`. See `guided-tour.instructions.md`. |
|
|
89
91
|
|
|
@@ -120,7 +120,7 @@ if (-not (Test-Path $pluginDir)) {
|
|
|
120
120
|
exit 2
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
$skillDirs = Get-ChildItem $skillsDir -Directory | Sort-Object Name
|
|
123
|
+
$skillDirs = Get-ChildItem $skillsDir -Directory | Where-Object { $_.Name -notmatch '^\.' -and $_.Name -notmatch '^_' } | Sort-Object Name
|
|
124
124
|
$skillNames = $skillDirs.Name
|
|
125
125
|
$instructionFiles = Get-ChildItem $instructionsDir -Filter '*.instructions.md' -EA SilentlyContinue
|
|
126
126
|
$promptFiles = Get-ChildItem $promptsDir -Filter '*.prompt.md' -EA SilentlyContinue
|
|
@@ -264,7 +264,7 @@ if (Test-Path $pluginJsonFile) {
|
|
|
264
264
|
$chain += $name
|
|
265
265
|
return $chain
|
|
266
266
|
}
|
|
267
|
-
$skillNames = (Get-ChildItem $skillsDir -Directory).Name
|
|
267
|
+
$skillNames = (Get-ChildItem $skillsDir -Directory | Where-Object { $_.Name -notmatch '^\.' -and $_.Name -notmatch '^_' }).Name
|
|
268
268
|
$promptStems = (Get-ChildItem (Join-Path $pluginDir 'prompts') -File -Filter '*.prompt.md' -ErrorAction SilentlyContinue) `
|
|
269
269
|
| ForEach-Object { $_.Name -replace '\.prompt\.md$','' }
|
|
270
270
|
$instructionStems = (Get-ChildItem (Join-Path $pluginDir 'instructions') -File -Filter '*.instructions.md' -ErrorAction SilentlyContinue) `
|
|
@@ -1663,7 +1663,7 @@ process.stdout.write(JSON.stringify(out));
|
|
|
1663
1663
|
}
|
|
1664
1664
|
|
|
1665
1665
|
$skillsRoot = Join-Path $Root 'plugin/skills'
|
|
1666
|
-
$skillDirs = Get-ChildItem -Path $skillsRoot -Directory | Where-Object { $_.Name -notin @('eval', 'self-check') }
|
|
1666
|
+
$skillDirs = Get-ChildItem -Path $skillsRoot -Directory | Where-Object { $_.Name -notin @('eval', 'self-check') -and $_.Name -notmatch '^_' }
|
|
1667
1667
|
foreach ($sd in $skillDirs) {
|
|
1668
1668
|
$evalsFile = Join-Path $sd.FullName 'evals/evals.json'
|
|
1669
1669
|
if (-not (Test-Path $evalsFile)) {
|
|
@@ -1778,6 +1778,136 @@ process.stdout.write(JSON.stringify(out));
|
|
|
1778
1778
|
}
|
|
1779
1779
|
}
|
|
1780
1780
|
|
|
1781
|
+
# === D35.log — State/log.md format and presence (v5.1.0+) ===
|
|
1782
|
+
# Validates that writer-touched State dirs have log.md and that entries match canonical format.
|
|
1783
|
+
# Operates on .testtmp/ fixtures or real engagement roots.
|
|
1784
|
+
$logFixtureDir = Join-Path $Root '.testtmp'
|
|
1785
|
+
$logTestDirs = @()
|
|
1786
|
+
if (Test-Path $logFixtureDir) {
|
|
1787
|
+
$logTestDirs += Get-ChildItem -Path $logFixtureDir -Recurse -Directory -ErrorAction SilentlyContinue |
|
|
1788
|
+
Where-Object { $_.Name -eq 'State' -and (Test-Path (Join-Path $_.FullName 'log.md')) }
|
|
1789
|
+
}
|
|
1790
|
+
# Also check real engagement roots if available
|
|
1791
|
+
foreach ($engRoot in (Resolve-EngagementRoots -RepoRoot $Root)) {
|
|
1792
|
+
Get-ChildItem -Path $engRoot -Directory -ErrorAction SilentlyContinue | ForEach-Object {
|
|
1793
|
+
$stateDir = Join-Path $_.FullName 'State'
|
|
1794
|
+
if (Test-Path $stateDir) { $logTestDirs += Get-Item $stateDir }
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
foreach ($sd in $logTestDirs) {
|
|
1799
|
+
$logMd = Join-Path $sd.FullName 'log.md'
|
|
1800
|
+
|
|
1801
|
+
# D35.log-exists
|
|
1802
|
+
if (-not (Test-Path $logMd)) {
|
|
1803
|
+
Add-Finding 'D35.log-exists' 'State log' 'warning' "State directory '$($sd.FullName)' has no log.md" "Add log.md per log-format.instructions.md. Run build-state to auto-create it." $sd.FullName 0
|
|
1804
|
+
continue
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
# D35.log-format — every ## [ heading matches canonical format
|
|
1808
|
+
$logLines = Get-Content -Path $logMd -ErrorAction SilentlyContinue
|
|
1809
|
+
$prevTs = $null
|
|
1810
|
+
$formatOk = $true
|
|
1811
|
+
$monotonicOk = $true
|
|
1812
|
+
foreach ($line in $logLines) {
|
|
1813
|
+
if ($line -match '^\#\#\s+\[') {
|
|
1814
|
+
if ($line -notmatch '^\#\#\s+\[\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\]\s+\S+\s+\|\s+.+$') {
|
|
1815
|
+
Add-Finding 'D35.log-format' 'State log' 'warning' "log.md heading does not match canonical format: $line" "Heading must be: ## [YYYY-MM-DD HH:MM] <op> | <title>. See log-format.instructions.md." $logMd 0
|
|
1816
|
+
$formatOk = $false
|
|
1817
|
+
break
|
|
1818
|
+
}
|
|
1819
|
+
# D35.log-monotonic — timestamps non-decreasing (newest first = decreasing)
|
|
1820
|
+
if ($line -match '^\#\#\s+\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\]') {
|
|
1821
|
+
try {
|
|
1822
|
+
$ts = [datetime]::ParseExact($Matches[1], 'yyyy-MM-dd HH:mm', $null)
|
|
1823
|
+
if ($prevTs -and $ts -gt $prevTs) {
|
|
1824
|
+
Add-Finding 'D35.log-monotonic' 'State log' 'warning' "log.md timestamps not reverse-chronological: $($Matches[1]) appears after an earlier timestamp" "Entries should be prepended (newest first). Re-order or re-run build-state." $logMd 0
|
|
1825
|
+
$monotonicOk = $false
|
|
1826
|
+
break
|
|
1827
|
+
}
|
|
1828
|
+
$prevTs = $ts
|
|
1829
|
+
} catch {}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
# === D36.contradictions — contradiction handling integrity (v5.1.0+) ===
|
|
1836
|
+
# Validates callout syntax, review-queue freshness, and build-state non-fenced preservation.
|
|
1837
|
+
$contradictionDirs = @()
|
|
1838
|
+
if (Test-Path $logFixtureDir) {
|
|
1839
|
+
$contradictionDirs += Get-ChildItem -Path $logFixtureDir -Recurse -Directory -ErrorAction SilentlyContinue |
|
|
1840
|
+
Where-Object { $_.Name -eq 'State' }
|
|
1841
|
+
}
|
|
1842
|
+
foreach ($engRoot in (Resolve-EngagementRoots -RepoRoot $Root)) {
|
|
1843
|
+
Get-ChildItem -Path $engRoot -Directory -ErrorAction SilentlyContinue | ForEach-Object {
|
|
1844
|
+
$stateDir = Join-Path $_.FullName 'State'
|
|
1845
|
+
if (Test-Path $stateDir) { $contradictionDirs += Get-Item $stateDir }
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
foreach ($sd in $contradictionDirs) {
|
|
1850
|
+
$mdFiles = Get-ChildItem -Path $sd.FullName -Recurse -Filter '*.md' -ErrorAction SilentlyContinue
|
|
1851
|
+
|
|
1852
|
+
# D36.callout-syntax — every > [!warning] Contradicted callout has required structure
|
|
1853
|
+
foreach ($mf in $mdFiles) {
|
|
1854
|
+
$content = Get-Content -Raw $mf.FullName -ErrorAction SilentlyContinue
|
|
1855
|
+
if (-not $content) { continue }
|
|
1856
|
+
if ($content -match '>\s*\[!warning\]\s*Contradicted') {
|
|
1857
|
+
# Must have 'by' clause and a following > [!info] block
|
|
1858
|
+
if ($content -notmatch '>\s*\[!warning\]\s*Contradicted\s+by\s+') {
|
|
1859
|
+
Add-Finding 'D36.callout-syntax' 'Contradictions' 'warning' "Callout in $($mf.Name) missing 'by <source>' clause" "Format: > [!warning] Contradicted by <skill> run <date>. See living-wiki.instructions.md." $mf.FullName 0
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
# D36.review-queue-fresh — if _review-queue.md has open items, most recent log entry is within 30 days
|
|
1865
|
+
$reviewQueue = Join-Path $sd.FullName '_review-queue.md'
|
|
1866
|
+
if (Test-Path $reviewQueue) {
|
|
1867
|
+
$rqContent = Get-Content -Raw $reviewQueue -ErrorAction SilentlyContinue
|
|
1868
|
+
# Check if review-queue has data rows (not just header + separator)
|
|
1869
|
+
$rqLines = ($rqContent -split '\r?\n') | Where-Object { $_ -match '^\|' -and $_ -notmatch '^\|\s*-' -and $_ -notmatch '^\|\s*Entity' }
|
|
1870
|
+
if ($rqLines.Count -gt 0) {
|
|
1871
|
+
# Has table rows (open items)
|
|
1872
|
+
$logMd2 = Join-Path $sd.FullName 'log.md'
|
|
1873
|
+
if (Test-Path $logMd2) {
|
|
1874
|
+
$logContent2 = Get-Content -Raw $logMd2
|
|
1875
|
+
$recentEntry = $false
|
|
1876
|
+
if ($logContent2 -match '(?m)^\#\#\s+\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\]') {
|
|
1877
|
+
try {
|
|
1878
|
+
$lastTs = [datetime]::ParseExact($Matches[1], 'yyyy-MM-dd HH:mm', $null)
|
|
1879
|
+
if (((Get-Date) - $lastTs).TotalDays -le 30) { $recentEntry = $true }
|
|
1880
|
+
} catch {}
|
|
1881
|
+
}
|
|
1882
|
+
if (-not $recentEntry) {
|
|
1883
|
+
Add-Finding 'D36.review-queue-fresh' 'Contradictions' 'warning' "_review-queue.md has open items but most recent log entry is >30 days old" "Run 'lint-state' or 'build-state' to refresh. Open contradictions need periodic review." $reviewQueue 0
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
# D36.no-silent-overwrite — build-state preserves non-fenced regions
|
|
1890
|
+
# This check validates the invariant by looking for the fencing pattern.
|
|
1891
|
+
# If State pages exist with kushi:auto fences AND content outside fences,
|
|
1892
|
+
# verify the file has not been fully regenerated (check mtime consistency).
|
|
1893
|
+
# For the fixture-based test: run build-state twice, assert non-fenced bytes unchanged.
|
|
1894
|
+
# In self-check, we do a structural check: if a page has content outside fences,
|
|
1895
|
+
# verify it also has content INSIDE fences (indicating incremental mode is active).
|
|
1896
|
+
foreach ($mf in $mdFiles) {
|
|
1897
|
+
$content = Get-Content -Raw $mf.FullName -ErrorAction SilentlyContinue
|
|
1898
|
+
if (-not $content) { continue }
|
|
1899
|
+
if ($content -notmatch 'kushi_state_page:\s*true') { continue }
|
|
1900
|
+
if ($content -match '<!-- kushi:auto:start') {
|
|
1901
|
+
# Good — page uses fencing. Verify fence pairs are balanced.
|
|
1902
|
+
$starts = ([regex]::Matches($content, '<!-- kushi:auto:start')).Count
|
|
1903
|
+
$ends = ([regex]::Matches($content, '<!-- kushi:auto:end')).Count
|
|
1904
|
+
if ($starts -ne $ends) {
|
|
1905
|
+
Add-Finding 'D36.no-silent-overwrite' 'Contradictions' 'warning' "$($mf.Name) has mismatched kushi:auto fence pairs ($starts starts, $ends ends)" "Every <!-- kushi:auto:start --> must have a matching <!-- kushi:auto:end -->. Fix the fencing." $mf.FullName 0
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1781
1911
|
# === Output ===
|
|
1782
1912
|
if ($Targeted) {
|
|
1783
1913
|
# Filter findings to those whose code, surface, file path, or message contain the substring.
|
|
@@ -88,7 +88,7 @@ function Get-TargetSkills {
|
|
|
88
88
|
return @(Get-Item -LiteralPath $d)
|
|
89
89
|
}
|
|
90
90
|
if ($All) {
|
|
91
|
-
return Get-ChildItem -Path $skillsRoot -Directory | Where-Object { $excludeFromAll -notcontains $_.Name }
|
|
91
|
+
return Get-ChildItem -Path $skillsRoot -Directory | Where-Object { $excludeFromAll -notcontains $_.Name -and $_.Name -notmatch '^_' }
|
|
92
92
|
}
|
|
93
93
|
throw "Specify -Skill <name> or -All."
|
|
94
94
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# State/answers/
|
|
2
|
+
|
|
3
|
+
This folder contains filed-back Q&A answers from `ask-project --file-back`.
|
|
4
|
+
|
|
5
|
+
Each file is named `YYYY-MM-DD_<slug>.md` and contains the question, answer, and source citations.
|
|
6
|
+
|
|
7
|
+
These are durable knowledge artifacts — they persist across refreshes and serve as a queryable FAQ for the project.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
kushi_state_hot: true
|
|
3
|
+
generated_at: "{{generated_at}}"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hot — {{project}}
|
|
7
|
+
|
|
8
|
+
> _Auto-generated. Entities touched in the last 7 days, ranked by recency. Content between fences is regenerated on every build-state run._
|
|
9
|
+
|
|
10
|
+
<!-- kushi:auto:start section="hot-entities" -->
|
|
11
|
+
{{hot_entities}}
|
|
12
|
+
<!-- kushi:auto:end section="hot-entities" -->
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
kushi_state_review_queue: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Review Queue
|
|
6
|
+
|
|
7
|
+
> _Open contradictions requiring human review. Updated by build-state when contradictions are detected. Cleared when resolved (auto or manual)._
|
|
8
|
+
|
|
9
|
+
| Entity | Property | Old value | New value | Flagged | Sources |
|
|
10
|
+
|---|---|---|---|---|---|
|
package/src/eval-runner.test.mjs
CHANGED
|
@@ -35,7 +35,7 @@ test('eval-runner: evals.schema.json validates structurally', () => {
|
|
|
35
35
|
test('eval-runner: every plugin/skills/<name>/ (except eval/) has evals/evals.json that parses', () => {
|
|
36
36
|
const skillsDir = path.join(repoRoot, 'plugin/skills');
|
|
37
37
|
const skills = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
38
|
-
.filter((d) => d.isDirectory() && d.name !== 'eval')
|
|
38
|
+
.filter((d) => d.isDirectory() && d.name !== 'eval' && !d.name.startsWith('_'))
|
|
39
39
|
.map((d) => d.name);
|
|
40
40
|
const missing = [];
|
|
41
41
|
for (const skill of skills) {
|