@su-record/vibe 4.2.20 → 4.2.22

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.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/dist/cli/common.js +4 -2
  5. package/dist/cli/common.js.map +1 -1
  6. package/dist/cli/heldout.js +59 -0
  7. package/dist/cli/heldout.js.map +1 -0
  8. package/dist/cli/human.js +5 -3
  9. package/dist/cli/human.js.map +1 -1
  10. package/dist/cli/internal.js +2 -2
  11. package/dist/cli/internal.js.map +1 -1
  12. package/dist/cli/memory.js +23 -12
  13. package/dist/cli/memory.js.map +1 -1
  14. package/dist/cli/setup.js +4 -2
  15. package/dist/cli/setup.js.map +1 -1
  16. package/dist/cli/work.js +4 -3
  17. package/dist/cli/work.js.map +1 -1
  18. package/dist/cli.js +1 -0
  19. package/dist/cli.js.map +1 -1
  20. package/dist/core/check.js +10 -2
  21. package/dist/core/check.js.map +1 -1
  22. package/dist/core/checks/review.js +6 -3
  23. package/dist/core/checks/review.js.map +1 -1
  24. package/dist/core/config.js.map +1 -1
  25. package/dist/core/consent.js +33 -0
  26. package/dist/core/consent.js.map +1 -1
  27. package/dist/core/heldout-run.js +25 -0
  28. package/dist/core/heldout-run.js.map +1 -0
  29. package/dist/core/heldout.js +167 -0
  30. package/dist/core/heldout.js.map +1 -0
  31. package/dist/core/intent.js +12 -5
  32. package/dist/core/intent.js.map +1 -1
  33. package/dist/core/knowledge-lint.js +4 -83
  34. package/dist/core/knowledge-lint.js.map +1 -1
  35. package/dist/core/knowledge.js +14 -4
  36. package/dist/core/knowledge.js.map +1 -1
  37. package/dist/core/ledger.js +19 -0
  38. package/dist/core/ledger.js.map +1 -1
  39. package/dist/core/scenarios.js +2 -0
  40. package/dist/core/scenarios.js.map +1 -1
  41. package/dist/core/status.js +79 -0
  42. package/dist/core/status.js.map +1 -0
  43. package/dist/install/plugin.js +1 -1
  44. package/dist/install/plugin.js.map +1 -1
  45. package/hooks/knowledge-auto.cjs +17 -16
  46. package/hooks/knowledge-journal.cjs +2 -1
  47. package/hooks/knowledge-lint.cjs +150 -0
  48. package/hooks/knowledge-projects.cjs +12 -1
  49. package/hooks/knowledge-upkeep.cjs +49 -0
  50. package/hooks/session-files.cjs +33 -1
  51. package/hooks/session-state.cjs +4 -2
  52. package/internal/agents/design-art-director.md +1 -1
  53. package/internal/agents/design-markup-reviewer.md +1 -1
  54. package/internal/guides/design.md +10 -0
  55. package/internal/guides/wiki.md +7 -2
  56. package/internal/skills/vibe-scope/SKILL.md +1 -1
  57. package/package.json +1 -1
  58. package/reviewers/design/1-markup-reviewer.md +1 -1
  59. package/reviewers/design/2-art-director.md +1 -1
  60. package/skills/vibe/SKILL.md +1 -1
@@ -0,0 +1,79 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import { readResults } from './check.js';
4
+ import { readConfig } from './config.js';
5
+ import { openQuestions } from './inbox.js';
6
+ import { loadScenarios } from './intent.js';
7
+ import { globalKnowledgeDir, knowledgeSettings } from './knowledge.js';
8
+ import { projectName } from './knowledge-projects.js';
9
+ import { isHuman } from './scenarios.js';
10
+ import { readState } from './state.js';
11
+ import { retestSummary } from './retest.js';
12
+ import { readText } from './store.js';
13
+ import path from 'node:path';
14
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
15
+ /** Knowledge turns this project had in the last week, counted from the wiki's log; null when the log cannot be read. */
16
+ function knowledgeTurns(root, home, now) {
17
+ try {
18
+ const log = readText(path.join(globalKnowledgeDir(home), 'log.md'));
19
+ if (log === null)
20
+ return 0;
21
+ const link = `[[projects/${projectName(fs.realpathSync(root), home, false)}]]`;
22
+ return log.split('\n').filter((line) => {
23
+ const at = /^- (\d{4}-\d{2}-\d{2} \d{2}:\d{2}) UTC · /.exec(line)?.[1];
24
+ return at && line.includes(link) && now.getTime() - Date.parse(`${at.replace(' ', 'T')}:00Z`) <= WEEK_MS;
25
+ }).length;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ export function projectSummary(root, home, now = new Date()) {
32
+ const results = readResults(root);
33
+ const gated = loadScenarios(root).filter((s) => !isHuman(s));
34
+ const times = gated.map((s) => results[s.id]?.at).filter((at) => !!at).sort();
35
+ const state = readState(root);
36
+ const knowledge = knowledgeSettings(home);
37
+ return {
38
+ checks: {
39
+ passed: gated.filter((s) => results[s.id]?.last === 'pass').length,
40
+ total: gated.length,
41
+ run: times.length ? state.runs : null,
42
+ at: times.at(-1) ?? null,
43
+ failing: gated.filter((s) => results[s.id]?.last === 'fail').map((s) => s.id),
44
+ stale: gated.filter((s) => results[s.id]?.last === 'stale').map((s) => s.id),
45
+ never: gated.filter((s) => !results[s.id]).map((s) => s.id),
46
+ },
47
+ settings: {
48
+ tokens: readConfig(root).tokens,
49
+ knowledge: knowledge.auto,
50
+ journal: knowledge.journal,
51
+ knowledgeTurns: knowledge.auto ? knowledgeTurns(root, home ?? process.env['VIBE_HOME_DIR'] ?? os.homedir(), now) : null,
52
+ retest: retestSummary(root, home).mode,
53
+ },
54
+ questions: openQuestions(root).filter((q) => !q.answer?.trim()).map((q) => q.question),
55
+ };
56
+ }
57
+ function ago(at, now) {
58
+ const minutes = Math.max(0, Math.round((now.getTime() - Date.parse(at)) / 60_000));
59
+ if (minutes < 60)
60
+ return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
61
+ const hours = Math.round(minutes / 60);
62
+ if (hours < 48)
63
+ return `${hours} hour${hours === 1 ? '' : 's'} ago`;
64
+ return `${Math.round(hours / 24)} days ago`;
65
+ }
66
+ export function summaryLines(s, now = new Date()) {
67
+ const c = s.checks;
68
+ const run = c.at === null ? 'never run — `vibe check --all`' : `last run r-${c.run}, ${ago(c.at, now)}`;
69
+ const failing = `${c.failing.length ? ` · failing: ${c.failing.join(', ')}` : ''}${c.stale.length ? ` · changed since it passed: ${c.stale.join(', ')}` : ''}`;
70
+ const turns = s.settings.knowledgeTurns === null ? '' : `, ${s.settings.knowledgeTurns} knowledge turn${s.settings.knowledgeTurns === 1 ? '' : 's'} here in 7 days`;
71
+ const knowledge = s.settings.knowledge ? `knowledge on (journal ${s.settings.journal ? 'on' : 'off'})${turns}` : 'knowledge off';
72
+ const waiting = s.questions.length;
73
+ return [
74
+ ` checks ${c.passed}/${c.total} passed · ${run}${failing}`,
75
+ ` settings tokens ${s.settings.tokens} · ${knowledge} · retest ${s.settings.retest}`,
76
+ ` questions ${waiting === 0 ? 'none waiting for you' : `${waiting} waiting for you: "${s.questions[0]}"`}`,
77
+ ];
78
+ }
79
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/core/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAaxC,wHAAwH;AACxH,SAAS,cAAc,CAAC,IAAY,EAAE,IAAY,EAAE,GAAS;IAC3D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;QACpE,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,cAAc,WAAW,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC;QAC/E,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;YACrC,MAAM,EAAE,GAAG,2CAA2C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACvE,OAAO,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC;QAC3G,CAAC,CAAC,CAAC,MAAM,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAa,EAAE,MAAY,IAAI,IAAI,EAAE;IAChF,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5F,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC1C,OAAO;QACL,MAAM,EAAE;YACN,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,MAAM;YAClE,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;YACrC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI;YACxB,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7E,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D;QACD,QAAQ,EAAE;YACR,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM;YAC/B,SAAS,EAAE,SAAS,CAAC,IAAI;YACzB,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,cAAc,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;YACvH,MAAM,EAAE,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI;SACvC;QACD,SAAS,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;KACvF,CAAC;AACJ,CAAC;AAED,SAAS,GAAG,CAAC,EAAU,EAAE,GAAS;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IACnF,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;IAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,GAAG,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;IACpE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,CAAiB,EAAE,MAAY,IAAI,IAAI,EAAE;IACpE,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnB,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;IACxG,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC/J,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,cAAc,kBAAkB,CAAC,CAAC,QAAQ,CAAC,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC;IACpK,MAAM,SAAS,GAAG,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC;IACjI,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC;IACnC,OAAO;QACL,eAAe,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,aAAa,GAAG,GAAG,OAAO,EAAE;QAC9D,sBAAsB,CAAC,CAAC,QAAQ,CAAC,MAAM,MAAM,SAAS,aAAa,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE;QACtF,eAAe,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,GAAG,OAAO,sBAAsB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE;KAC5G,CAAC;AACJ,CAAC"}
@@ -12,7 +12,7 @@ import { pluginTree } from './tree.js';
12
12
  * manifest, the single entry skill and the notification hooks.
13
13
  */
14
14
  const SKILL_NAMES = ['vibe'];
15
- const HOOK_FILES = ['notify.js', 'session.js', 'private-store.cjs', 'session-files.cjs', 'session-state.cjs', 'session-context.cjs', 'session-repair.cjs', 'knowledge-auto.cjs', 'knowledge-projects.cjs', 'knowledge-journal.cjs', 'retest.cjs', 'journal.cjs', 'obsidian.cjs', 'slice-command.cjs', 'slice-store.cjs', 'slice-guard.cjs'];
15
+ const HOOK_FILES = ['notify.js', 'session.js', 'private-store.cjs', 'session-files.cjs', 'session-state.cjs', 'session-context.cjs', 'session-repair.cjs', 'knowledge-auto.cjs', 'knowledge-projects.cjs', 'knowledge-lint.cjs', 'knowledge-upkeep.cjs', 'knowledge-journal.cjs', 'retest.cjs', 'journal.cjs', 'obsidian.cjs', 'slice-command.cjs', 'slice-store.cjs', 'slice-guard.cjs'];
16
16
  export const MARKETPLACE_NAME = 'vibe-local';
17
17
  /** The tree lives under ~/.config/vibe so that `.vibe/` keeps one meaning: a project's records. ≤ 4.1.7 put it at ~/.vibe/plugin. */
18
18
  export function pluginPaths(home = os.homedir()) {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/install/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/install/plugin.ts"],"names":[],"mappings":"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"}
@@ -8,10 +8,11 @@ const fs = require('node:fs');
8
8
  const os = require('node:os');
9
9
  const path = require('node:path');
10
10
  const { inside, readPrivate, writePrivate } = require('./private-store.cjs');
11
- const { optional, digest, revision, projectFingerprint } = require('./session-files.cjs');
11
+ const { optional, digest, revision, changeStamp } = require('./session-files.cjs');
12
12
  const { identity, hostRoot } = require('./session-state.cjs');
13
13
  const { connectVault } = require('./obsidian.cjs');
14
- const { projectName, linkProject, indexProject } = require('./knowledge-projects.cjs');
14
+ const { projectName, linkProject, indexProject, projectPage } = require('./knowledge-projects.cjs');
15
+ const upkeep = require('./knowledge-upkeep.cjs');
15
16
  const journal = require('./knowledge-journal.cjs');
16
17
 
17
18
  const defaultHome = () => process.env.VIBE_HOME_DIR || os.homedir();
@@ -28,6 +29,8 @@ function settings(home) {
28
29
  skills: raw.skills === 'create' ? 'create' : 'propose',
29
30
  exclude: Array.isArray(raw.exclude) ? raw.exclude.filter(e => typeof e === 'string' && path.isAbsolute(e)) : [],
30
31
  journal: raw.journal !== false,
32
+ review: Number.isInteger(raw.review) && raw.review >= 0 ? raw.review : 10,
33
+ reviewDays: Number.isInteger(raw.reviewDays) && raw.reviewDays >= 0 ? raw.reviewDays : 14,
31
34
  };
32
35
  } catch { return { auto: false }; }
33
36
  }
@@ -43,7 +46,7 @@ function place(payload, home, exclude) {
43
46
  }
44
47
  function snapshot(payload, home, exclude) {
45
48
  const at = place(payload, home, exclude);
46
- return { ...at, files: projectFingerprint(at.root) };
49
+ return { ...at, files: changeStamp(at.root) };
47
50
  }
48
51
 
49
52
  /** UserPromptSubmit and PostToolUse(Bash): one journal line, silently; nothing when knowledge or the journal is off. */
@@ -57,21 +60,16 @@ function observe(kind, payload, home = defaultHome()) {
57
60
  } catch { /* the work goes on without a journal line */ }
58
61
  }
59
62
 
60
- function client(env = process.env) {
61
- if (env.VIBE_CLIENT) return ['codex', 'chatgpt'].includes(env.VIBE_CLIENT) ? 'codex' : 'claude';
62
- return env.CODEX_THREAD_ID && !env.CLAUDE_SESSION_ID ? 'codex' : 'claude';
63
- }
64
-
65
- function logLine(root, what, project, now = new Date()) {
63
+ function logLine(root, what, project, now = new Date(), client = journal.clientOf({})) {
66
64
  let at = 'unversioned';
67
65
  try { at = revision(root).slice(0, 12); } catch { /* the line still records the turn */ }
68
- return `- ${now.toISOString().slice(0, 16).replace('T', ' ')} UTC · [[projects/${project}]] · ${client()} · ${at} · ${what}\n`;
66
+ return `- ${now.toISOString().slice(0, 16).replace('T', ' ')} UTC · [[projects/${project}]] · ${client} · ${at} · ${what}\n`;
69
67
  }
70
68
 
71
69
  function remembered(root, id, home) {
72
70
  try {
73
71
  const raw = JSON.parse(readPrivate(root, entry(root, id), 4096, home) ?? 'null');
74
- return raw && typeof raw.files === 'string' && Number.isInteger(raw.turns) ? raw : null;
72
+ return raw && (typeof raw.files === 'string' || raw.files === null) && Number.isInteger(raw.turns) ? raw : null;
75
73
  } catch { return null; }
76
74
  }
77
75
 
@@ -105,12 +103,13 @@ const REASON = (places, skills, observed) => `[vibe] Knowledge turn: the user tu
105
103
 
106
104
  /** Stop: after a changed turn, or `turns` conversation turns, log the fact and give the model one knowledge turn. */
107
105
  function stop(payload, home = defaultHome()) {
108
- const { auto, dir, turns: every, skills, exclude, journal: on } = settings(home);
106
+ const { auto, dir, turns: every, skills, exclude, journal: on, review, reviewDays } = settings(home);
109
107
  if (!auto || payload.stop_hook_active === true) return {};
110
108
  try {
111
109
  const now = snapshot(payload, home, exclude);
112
110
  const before = remembered(now.root, now.id, home);
113
- const changed = before !== null && before.files !== now.files;
111
+ // An unknown stamp (the walk ran out of time) is never a change; the conversation turns still count.
112
+ const changed = before !== null && now.files !== null && before.files !== null && before.files !== now.files;
114
113
  const turns = (before?.turns ?? 0) + 1;
115
114
  if (before === null || (!changed && (every === 0 || turns < every))) {
116
115
  writePrivate(now.root, entry(now.root, now.id), JSON.stringify({ files: now.files, turns: before === null ? 0 : turns }), home);
@@ -118,11 +117,13 @@ function stop(payload, home = defaultHome()) {
118
117
  }
119
118
  fs.mkdirSync(dir, { recursive: true });
120
119
  const name = projectName(now.root, home);
121
- fs.appendFileSync(path.join(dir, 'log.md'), logLine(now.root, changed ? 'files changed' : 'conversation', name));
120
+ projectPage(dir, name, now.root);
121
+ fs.appendFileSync(path.join(dir, 'log.md'), logLine(now.root, changed ? 'files changed' : 'conversation', name, new Date(), journal.clientOf(payload)));
122
+ const wiki = upkeep.tidy(dir, home) + upkeep.review(dir, home, review, reviewDays);
122
123
  // A wiki inside the project changes the files it was just compared with; look again so that is not seen as work.
123
- const files = inside(now.root, dir) ? projectFingerprint(now.root) : now.files;
124
+ const files = inside(now.root, dir) ? changeStamp(now.root) : now.files;
124
125
  writePrivate(now.root, entry(now.root, now.id), JSON.stringify({ files, turns: 0 }), home);
125
- return { decision: 'block', reason: REASON(PLACES(dir, now.root, name), skills, on ? observed(now, home) : '') };
126
+ return { decision: 'block', reason: REASON(PLACES(dir, now.root, name), skills, on ? observed(now, home) : '') + wiki };
126
127
  } catch { return {}; }
127
128
  }
128
129
 
@@ -78,6 +78,7 @@ function outcome(response) {
78
78
  return typeof response.output === 'string' ? exitText(response.output) : null;
79
79
  }
80
80
 
81
+ /** The client behind a hook call: the transcript path the payload names, then the environment. */
81
82
  function clientOf(payload, env = process.env) {
82
83
  const transcript = typeof payload.transcript_path === 'string' ? payload.transcript_path.replaceAll('\\', '/') : '';
83
84
  if (transcript.includes('/.codex/')) return 'codex';
@@ -199,4 +200,4 @@ function brief(summary) {
199
200
  return `; the journal holds ${r.prompts.length} prompts and ${r.corrections.length} corrections since the last knowledge turn and ${repeats} repeats over ${summary.days} days: read them with \`vibe knowledge journal\` and work from them rather than from memory`;
200
201
  }
201
202
 
202
- module.exports = { mask, segments, commandHeads, outcome, runOutcome, entryFor, read, append, summarize, brief, fileName };
203
+ module.exports = { mask, segments, commandHeads, outcome, runOutcome, clientOf, entryFor, read, append, summarize, brief, fileName };
@@ -0,0 +1,150 @@
1
+ // The wiki's structure, checked and kept without a model: broken links, orphan pages, long pages, unknown types and
2
+ // pages sharing a title. `fixWiki` settles what needs no judgement (an orphan joins the index as a marked line, a
3
+ // marked line whose page is gone is removed, a project page log.md links to is created); the knowledge turn names
4
+ // the rest. Shared by `vibe knowledge lint` and the session hook, which runs it within the client's Stop time limit.
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { readNames, projectPage, writeIfChanged } = require('./knowledge-projects.cjs');
8
+
9
+ const MAX_PAGES = 2000;
10
+ const LONG_LINES = 200;
11
+ /** Pages that are entry points or append-only, so nothing has to link to them. */
12
+ const ROOTS = new Set(['index', 'log']);
13
+ /** What a page is, so the model knows whether to follow it, reuse it, avoid it or just know it; the wiki guide names them. */
14
+ const TYPES = ['decision', 'method', 'problem', 'fact', 'project', 'research', 'skill', 'proposal', 'source'];
15
+ /** Ends an index line vibe wrote, so vibe removes only its own lines; Obsidian's reading view hides it. */
16
+ const AUTO = '<!-- vibe:auto -->';
17
+
18
+ /**
19
+ * The wiki's pages, and the pages of each project's shared knowledge it shows through a folder link
20
+ * (`projects/<name>/`). Those are kept in the project, so they resolve links and are reported as unlinked, not orphans.
21
+ */
22
+ function wikiPages(dir) {
23
+ const files = [];
24
+ const shared = new Set();
25
+ const seen = new Set();
26
+ let partial = false;
27
+ const walk = (current, linked) => {
28
+ if (seen.has(fs.realpathSync(current))) return;
29
+ seen.add(fs.realpathSync(current));
30
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
31
+ if (entry.name.startsWith('.')) continue;
32
+ if (files.length >= MAX_PAGES) { partial = true; return; }
33
+ const file = path.join(current, entry.name);
34
+ if (entry.isDirectory()) walk(file, linked);
35
+ else if (entry.isSymbolicLink() && fs.statSync(file, { throwIfNoEntry: false })?.isDirectory()) walk(file, true);
36
+ else if (entry.isFile() && entry.name.endsWith('.md')) { files.push(file); if (linked) shared.add(file); }
37
+ }
38
+ };
39
+ if (fs.existsSync(dir)) walk(dir, false);
40
+ return { files: files.sort(), shared, partial };
41
+ }
42
+
43
+ /** `[[target]]`, `[[target|label]]` and `[[target#heading]]` give `target`; links inside code are still counted. */
44
+ function wikiLinks(text) {
45
+ return [...text.matchAll(/\[\[([^\]|#\n]+)(?:[#|][^\]\n]*)?\]\]/g)].map(m => m[1].trim()).filter(Boolean);
46
+ }
47
+
48
+ /** The `title` property, else the first `# heading`, else the file name. */
49
+ function pageTitle(text, file) {
50
+ const front = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
51
+ const stated = front && /^title:\s*["']?(.+?)["']?\s*$/m.exec(front[1])?.[1];
52
+ const heading = /^#\s+(.+?)\s*$/m.exec(front ? text.slice(front[0].length) : text)?.[1];
53
+ return stated || heading || path.basename(file, '.md');
54
+ }
55
+
56
+ /** Each page's name as a link target, and every name a link may use for it (path from the root or file name alone). */
57
+ function names(dir, files) {
58
+ const name = file => path.relative(dir, file).split(path.sep).join('/').replace(/\.md$/, '');
59
+ const known = new Map();
60
+ for (const file of files) {
61
+ known.set(name(file).toLowerCase(), name(file));
62
+ known.set(path.basename(file, '.md').toLowerCase(), name(file));
63
+ }
64
+ return { name, resolve: target => known.get(target.replace(/\.md$/, '').toLowerCase()) };
65
+ }
66
+
67
+ /**
68
+ * Broken links, pages nothing links to, pages too long to read in one pass, unknown types and pages sharing a
69
+ * title. A link resolves the way Obsidian resolves it; a link from log.md finds a page but does not make it reachable.
70
+ */
71
+ function lintWiki(dir) {
72
+ const { files, shared, partial } = wikiPages(dir);
73
+ const { name, resolve } = names(dir, files);
74
+ const broken = [], long = [], badType = [];
75
+ const linked = new Set();
76
+ const titles = new Map();
77
+ for (const file of files) {
78
+ const text = fs.readFileSync(file, 'utf8');
79
+ const lines = text.split('\n').length;
80
+ const type = /^---\r?\n(?:[^\n]*\n)*?type:\s*["']?([^"'\n]+?)["']?\s*\n(?:[^\n]*\n)*?---/.exec(text)?.[1];
81
+ // Source material (a transcript, a web clipping, a video analysis) is long by nature and never split.
82
+ if (lines > LONG_LINES && type?.toLowerCase() !== 'source') long.push({ page: name(file), lines });
83
+ if (type !== undefined && !TYPES.includes(type.toLowerCase())) badType.push({ page: name(file), type });
84
+ if (!shared.has(file) && !ROOTS.has(name(file))) {
85
+ const title = pageTitle(text, file).toLowerCase();
86
+ titles.set(title, [...(titles.get(title) ?? []), name(file)]);
87
+ }
88
+ for (const target of wikiLinks(text)) {
89
+ const page = resolve(target);
90
+ if (page === undefined) broken.push({ page: name(file), target });
91
+ else if (page !== name(file) && name(file) !== 'log') linked.add(page);
92
+ }
93
+ }
94
+ const orphans = files.filter(file => !shared.has(file)).map(name).filter(page => !linked.has(page) && !ROOTS.has(page));
95
+ const unlinked = files.filter(file => shared.has(file)).map(name).filter(page => !linked.has(page));
96
+ const duplicates = [...titles].filter(([, pages]) => pages.length > 1).map(([, pages]) => pages);
97
+ return { dir, pages: files.length, broken, orphans, unlinked, long, badType, duplicates, partial };
98
+ }
99
+
100
+ /**
101
+ * Settle what needs no judgement: create each project page log.md links to that is missing, add every orphan to
102
+ * index.md as a marked line, and remove marked lines whose page is gone. A person's line is never touched; its
103
+ * broken link stays in the lint. Returns what changed and the lint after it.
104
+ */
105
+ function fixWiki(dir, home) {
106
+ const created = [];
107
+ const log = path.join(dir, 'log.md');
108
+ if (fs.existsSync(log)) {
109
+ const { files } = wikiPages(dir);
110
+ const { resolve } = names(dir, files);
111
+ const roots = readNames(home);
112
+ for (const target of new Set(wikiLinks(fs.readFileSync(log, 'utf8')))) {
113
+ const project = /^projects\/([^/]+)$/.exec(target.replace(/\.md$/, ''))?.[1];
114
+ if (project && resolve(target) === undefined && projectPage(dir, project, roots[project])) created.push(`projects/${project}`);
115
+ }
116
+ }
117
+ const { files, shared } = wikiPages(dir);
118
+ const { name, resolve } = names(dir, files);
119
+ const index = path.join(dir, 'index.md');
120
+ const text = fs.existsSync(index) ? fs.readFileSync(index, 'utf8') : '# Index\n';
121
+ const removed = [];
122
+ const kept = text.split('\n').filter(line => {
123
+ const target = line.includes(AUTO) ? wikiLinks(line)[0] : undefined;
124
+ if (target === undefined || resolve(target) !== undefined) return true;
125
+ removed.push(target);
126
+ return false;
127
+ });
128
+ const added = lintWiki(dir).orphans.map(page => {
129
+ const file = files.find(f => !shared.has(f) && name(f) === page);
130
+ return { page, title: pageTitle(fs.readFileSync(file, 'utf8'), file).replace(/\s+/g, ' ') };
131
+ });
132
+ if (added.length || removed.length) {
133
+ const body = kept.join('\n').trimEnd();
134
+ writeIfChanged(index, `${body}\n${added.map(a => `- [[${a.page}]] — ${a.title} ${AUTO}\n`).join('')}`);
135
+ }
136
+ return { created, added, removed, lint: lintWiki(dir) };
137
+ }
138
+
139
+ /** Pages changed since `since` (index and log aside) and the pages they link to, for a review turn. */
140
+ function reviewScope(dir, since) {
141
+ const { files } = wikiPages(dir);
142
+ const { name, resolve } = names(dir, files);
143
+ const changed = files.filter(file => !ROOTS.has(name(file)) && fs.statSync(file).mtimeMs > since);
144
+ const linked = new Set();
145
+ for (const file of changed) for (const target of wikiLinks(fs.readFileSync(file, 'utf8'))) linked.add(resolve(target));
146
+ const pages = changed.map(name);
147
+ return { changed: pages, linked: [...linked].filter(page => page && !pages.includes(page) && !ROOTS.has(page)).sort() };
148
+ }
149
+
150
+ module.exports = { TYPES, AUTO, wikiPages, wikiLinks, pageTitle, lintWiki, fixWiki, reviewScope };
@@ -110,4 +110,15 @@ function indexProject(root, wiki, name) {
110
110
  }
111
111
  }
112
112
 
113
- module.exports = { projectName, linkProject, indexProject };
113
+ /**
114
+ * Create the project's page when it is missing, so a log line's `[[projects/<name>]]` always resolves. Returns
115
+ * whether it created one; an existing page is left as it is.
116
+ */
117
+ function projectPage(wiki, name, root) {
118
+ const page = path.join(wiki, 'projects', `${name}.md`);
119
+ if (fs.existsSync(page)) return false;
120
+ writeIfChanged(page, `---\ntype: project\n---\n# ${name}\n${typeof root === 'string' ? `\nFolder: \`${root}\`\n` : ''}`);
121
+ return true;
122
+ }
123
+
124
+ module.exports = { readNames, projectName, linkProject, indexProject, projectPage, writeIfChanged };
@@ -0,0 +1,49 @@
1
+ // What a knowledge turn adds about the wiki itself: the lint fix runs first, and the reason names only what it could
2
+ // not settle; after enough changed pages or days, the turn also reviews those pages and the pages they link.
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { fixWiki, reviewScope } = require('./knowledge-lint.cjs');
6
+ const { writeIfChanged } = require('./knowledge-projects.cjs');
7
+
8
+ const DAY = 86400000;
9
+ const SHOWN = 3;
10
+ const LISTED = 20;
11
+
12
+ const few = (items, show) => `${items.slice(0, SHOWN).map(show).join(', ')}${items.length > SHOWN ? ` and ${items.length - SHOWN} more` : ''}`;
13
+
14
+ /** Run the fix, then a clause naming what needs judgement, or '' when nothing is left. */
15
+ function tidy(dir, home) {
16
+ try {
17
+ const { lint } = fixWiki(dir, home);
18
+ const left = [
19
+ lint.broken.length ? `broken links ${few(lint.broken, b => `${b.page} → [[${b.target}]]`)}` : '',
20
+ lint.long.length ? `long pages ${few(lint.long, l => `${l.page} (${l.lines} lines)`)}` : '',
21
+ lint.badType.length ? `unknown types ${few(lint.badType, b => `${b.page} "${b.type}"`)}` : '',
22
+ lint.duplicates.length ? `pages sharing a title ${few(lint.duplicates, pages => pages.join(' = '))}` : '',
23
+ ].filter(Boolean);
24
+ return left.length ? ` The wiki lint fixed what needs no judgement; left for you: ${left.join('; ')}. Settle them as the guide's "Keep the connections" says (\`vibe knowledge lint\` lists all).` : '';
25
+ } catch { return ''; }
26
+ }
27
+
28
+ const stateFile = home => path.join(home, '.config', 'vibe', 'knowledge-review.json');
29
+
30
+ /**
31
+ * A review clause when `pages` pages changed since the last review, or one did and `days` days passed; '' otherwise.
32
+ * The first call only sets the starting point, so turning this on never reviews the whole wiki at once.
33
+ */
34
+ function review(dir, home, pages, days, now = Date.now()) {
35
+ if (!pages) return '';
36
+ try {
37
+ let since = null;
38
+ try { since = JSON.parse(fs.readFileSync(stateFile(home), 'utf8')).at; } catch { /* first review */ }
39
+ if (!Number.isFinite(since)) { writeIfChanged(stateFile(home), JSON.stringify({ at: now })); return ''; }
40
+ const scope = reviewScope(dir, since);
41
+ const due = scope.changed.length >= pages || (scope.changed.length > 0 && days > 0 && now - since >= days * DAY);
42
+ if (!due) return '';
43
+ writeIfChanged(stateFile(home), JSON.stringify({ at: now }));
44
+ const list = items => `${items.slice(0, LISTED).map(p => `[[${p}]]`).join(', ')}${items.length > LISTED ? ` and ${items.length - LISTED} more` : ''}`;
45
+ return ` This is also a review turn (${scope.changed.length} pages changed since ${new Date(since).toISOString().slice(0, 10)}): read ${list(scope.changed)}${scope.linked.length ? ` and the pages they link, ${list(scope.linked)},` : ''} for claims that contradict each other, dated claims likely out of date and decisions a later one replaced, and correct them as the guide's "Review" says.`;
46
+ } catch { return ''; }
47
+ }
48
+
49
+ module.exports = { tidy, review, stateFile };
@@ -71,6 +71,38 @@ function projectFingerprint(root) {
71
71
  }
72
72
  return hash.digest('hex');
73
73
  }
74
+ const UNWATCHED = new Set(['.git', 'node_modules', '.claude', '.codex', '.agents', '.venv', 'venv', '__pycache__', '.next', '.cache', 'dist', 'build', 'target']);
75
+ /**
76
+ * Whether a turn changed the project, cheaply enough for any real project: each file's name, size and modification
77
+ * time, a link's target without following it, generated folders skipped. Unlike projectFingerprint it reads no
78
+ * content and refuses nothing, so a large media file, a link or a big tree no longer turns session knowledge off.
79
+ * Past `max` entries the rest is not looked at (deterministically); past the time limit the answer is null, unknown.
80
+ */
81
+ function changeStamp(root, max = 20000, ms = 1000) {
82
+ const hash = createHash('sha256');
83
+ const queue = [''];
84
+ const deadline = Date.now() + ms;
85
+ let count = 0;
86
+ while (queue.length) {
87
+ const relative = queue.shift();
88
+ let entries;
89
+ try { entries = fs.readdirSync(path.join(root, relative), { withFileTypes: true }); } catch { continue; }
90
+ for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
91
+ if (++count > max) return hash.digest('hex');
92
+ if (Date.now() > deadline) return null;
93
+ if (UNWATCHED.has(entry.name)) continue;
94
+ const name = path.posix.join(relative, entry.name);
95
+ if (name.startsWith('.vibe/') && !/^\.vibe\/(?:intent\.md|scenarios\.yaml|source-basis\.json|config\.json|regressions(?:\/.*)?)$/.test(name)) continue;
96
+ if (entry.isDirectory()) { queue.push(name); continue; }
97
+ try {
98
+ const stat = fs.lstatSync(path.join(root, name));
99
+ const link = entry.isSymbolicLink() ? fs.readlinkSync(path.join(root, name)) : '';
100
+ hash.update(`${name}\0${stat.size}\0${stat.mtimeMs}\0${link}\n`);
101
+ } catch { /* gone between the listing and the look */ }
102
+ }
103
+ }
104
+ return hash.digest('hex');
105
+ }
74
106
  function readPayload() {
75
107
  const bytes = Buffer.alloc(65537);
76
108
  let length = 0, read;
@@ -78,4 +110,4 @@ function readPayload() {
78
110
  if (length > 65536) throw new Error('hook payload limit');
79
111
  return length ? JSON.parse(bytes.subarray(0, length).toString('utf8')) : {};
80
112
  }
81
- module.exports = { bounded, optional, json, digest, revision, projectFingerprint, readPayload };
113
+ module.exports = { bounded, optional, json, digest, revision, projectFingerprint, changeStamp, readPayload };
@@ -6,6 +6,8 @@ const { repairData, repairMessage } = require('./session-repair.cjs');
6
6
  const { contextStatus } = require('./session-context.cjs');
7
7
 
8
8
  const ID = /^[a-z0-9][a-z0-9-]{0,39}$/;
9
+ // A gate may also be a sealed exam, which is not a scenario in scenarios.yaml and carries a prefix.
10
+ const GATE_ID = /^(?:heldout:)?[a-z0-9][a-z0-9-]{0,39}$/;
9
11
  const HASH = /^[a-f0-9]{16,64}$/;
10
12
  const STATUSES = ['pass', 'fail', 'pending', 'blocked', 'stale', 'handoff'];
11
13
  const name = (kind, value) => `${kind}-${digest(value)}.json`;
@@ -63,7 +65,7 @@ function workflow(root) {
63
65
  function validEvidence(input) {
64
66
  return input && /^r-\d+$/.test(input.run) && HASH.test(input.intentHash) && typeof input.done === 'boolean' &&
65
67
  Array.isArray(input.scenarios) && input.scenarios.length <= 1000 &&
66
- input.scenarios.every(s => s && ID.test(s.id) && STATUSES.includes(s.status)) &&
68
+ input.scenarios.every(s => s && GATE_ID.test(s.id) && STATUSES.includes(s.status)) &&
67
69
  new Set(input.scenarios.map(s => s.id)).size === input.scenarios.length &&
68
70
  (!input.done || input.scenarios.every(s => s.status === 'pass'));
69
71
  }
@@ -104,7 +106,7 @@ function structuralStatus(root, binding) {
104
106
  const flow = workflow(root);
105
107
  const currentRevision = revision(root);
106
108
  const scenarios = proof?.scenarios ?? binding.scenarios;
107
- if (scenarios.length > 1000 || scenarios.some(s => !ID.test(s.id))) throw new Error('invalid private scenarios');
109
+ if (scenarios.length > 1000 || scenarios.some(s => !GATE_ID.test(s.id))) throw new Error('invalid private scenarios');
108
110
  const filesFresh = Boolean(proof && proof.intentHash === flow.intentHash && proof.revision === currentRevision && proof.files === projectFingerprint(root));
109
111
  const fresh = Boolean(filesFresh && proof.workflow === flow.changes);
110
112
  const complete = Boolean(fresh && proof.done && proof.scenarios.length && proof.scenarios.every(s => s.status === 'pass') && !flow.waiting.length && !flow.handed.length && !flow.approvalWaiting && !flow.abandoned);
@@ -7,7 +7,7 @@ model: inherit
7
7
 
8
8
  You are an art director seeing this work for the first time. Judge only whether the design is fit to ship — do not rewrite it.
9
9
 
10
- The caller gives you the design brief as the editorial contract (who uses this, how often, the task, and any stated brand tokens or constraints), the evidence ledger (real data sources, real copy, confirmed brand values, and what is unconfirmed or placeholder), and the full source, already passed by the markup reviewer, under `## Source`. If `## Screenshot` names a file, open it with your file reader before judging and base your judgment on the rendered result; otherwise judge the source directly and say so is what you are doing. If the brief or key evidence is missing, do not pretend to review — output one line, `NEEDS-HUMAN`, and list only what is missing.
10
+ The caller gives you the design brief as the editorial contract (who uses this, how often, the task, and any stated brand tokens or constraints), the evidence ledger (real data sources, real copy, confirmed brand values, and what is unconfirmed or placeholder), and the full source, already passed by the markup reviewer, under `## Source`. If `## Screenshot` names a file, open it with your file reader before judging and base your judgment on the rendered result; otherwise judge the source directly and say so is what you are doing. If `## Confirmed mockup` names a file, open it too: the person approved that mockup before the work was built, so a visible departure from it (layout, hierarchy, content, states it shows) is a defect unless the brief allows it, and the mockup's own choices are not grounds for rejection. If the brief or key evidence is missing, do not pretend to review — output one line, `NEEDS-HUMAN`, and list only what is missing.
11
11
 
12
12
  What to check:
13
13
  - Hierarchy: what is seen first, and whether that is what the stated task in the brief needs seen first
@@ -7,7 +7,7 @@ model: inherit
7
7
 
8
8
  You are a front-end lead seeing this source for the first time. Judge defects only — do not rewrite the source.
9
9
 
10
- The caller gives you the design brief as the editorial contract (who uses this, how often, the task, and any stated brand tokens or constraints), the evidence ledger (real data sources, real copy, confirmed brand values, and what is unconfirmed or placeholder), the full source under `## Source` (each file numbered line by line), and optionally `## Screenshot` naming a rendered image path. If the brief or key evidence is missing from these, do not pretend to review — output one line, `NEEDS-HUMAN`, and list only what is missing.
10
+ The caller gives you the design brief as the editorial contract (who uses this, how often, the task, and any stated brand tokens or constraints), the evidence ledger (real data sources, real copy, confirmed brand values, and what is unconfirmed or placeholder), the full source under `## Source` (each file numbered line by line), and optionally `## Screenshot` naming a rendered image path and `## Confirmed mockup` naming the mockup the person approved, which the art director compares against; you judge the markup. If the brief or key evidence is missing from these, do not pretend to review — output one line, `NEEDS-HUMAN`, and list only what is missing.
11
11
 
12
12
  What to check:
13
13
  - Semantic structure and landmarks: headings in one logical order, lists as `<ul>`/`<ol>`, buttons as `<button>`, links as `<a>` with a real `href`, regions marked with `<nav>`/`<main>`/`<header>`/`<footer>` rather than unlabelled `<div>`s
@@ -4,6 +4,16 @@ Use for UI creation, visual changes, complaints about design or a supplied refer
4
4
 
5
5
  Apply antislop while designing, without a separate request: each section, card, decoration and motion should serve real content or an interaction. Avoid fabricated metrics, interchangeable marketing copy and repeated card grids that hide the information hierarchy. Preserve an established visual language; gradients, rounded cards and other common patterns are not defects by themselves. Fix specific hierarchy, readability or interaction problems rather than adding novelty or a mandatory design-review pass.
6
6
 
7
+ ## Show a mockup first
8
+
9
+ When the result is a new screen or page, show the person a mockup before building it, made with whatever the host offers: a canvas or artifact preview, an HTML file opened in the browser, a generated image. Do not prescribe one method; use what the current host can show. Revise it with them until they confirm it, then build back from the confirmed mockup: its layout, content and states are the target, and a departure needs their yes.
10
+
11
+ Save the confirmed mockup in the project (for example `design/mockups/<name>.html` or `.png`) and list it in the draft's `sources`, so the approval is bound to it and a later change to the mockup voids it. Confirming the mockup and approving the scope are one message, not two ceremonies.
12
+
13
+ When the mockup is an image, convert it to HTML before building on it, render that HTML and compare it with the image; name in chat what could not be carried over (an effect, a font, an illustration). The image stays the reference, not the conversion.
14
+
15
+ A small change inside an established design needs no mockup; neither does a fix the person described exactly. If the host can show nothing, say so and describe the layout in words before building.
16
+
7
17
  ## Preserve a coherent direction
8
18
 
9
19
  Read the relevant existing design documentation, `DESIGN.md`, theme values and reusable components first. Existing project conventions take priority unless the user requests a redesign. A new page within an established design needs no new reference search. Small fixes need no new design document.
@@ -40,7 +40,8 @@ updated: 2026-10-16
40
40
  - `summary` is what lists and search results show, so write the conclusion, not "notes about X"; put it in double quotes when it holds a colon.
41
41
  - `tags`: one to five, reusing tags other pages already have (search finds them); lowercase, no spaces.
42
42
  - `aliases`: other names the user or a model would search for: synonyms, abbreviations and the user's own words in their language. This is the only place a record holds words outside English, so search in the user's language finds the page.
43
- - `type`, one of: `decision` (follow it), `method` (reuse it), `problem` (avoid or solve it), `fact`, `project`, `research`, `skill`, `proposal`. `vibe knowledge lint` reports any other value.
43
+ - `type`, one of: `decision` (follow it), `method` (reuse it), `problem` (avoid or solve it), `fact`, `project`, `research`, `skill`, `proposal`, `source`. `vibe knowledge lint` reports any other value.
44
+ - `source`: raw material kept as evidence (a transcript, a web clipping, a video analysis; voin marks meeting transcripts so). Never rewrite or split it; an organised page holds the conclusions and links it for its claims. The lint skips its length, not its links.
44
45
  - `supersedes: "[[old-page]]"` on a page that replaces another, and `status: archived` on a finished project or knowledge no longer in force. Search ranks both lower and marks them, so keep the old page for its reasons instead of deleting it.
45
46
  - One subject per page; a page covering two subjects is two pages linked to each other. Keep `## Related` at the end for links that are not already in the text.
46
47
 
@@ -80,7 +81,11 @@ Keep `proposals.md` in the wiki (`type: proposal` pages or one line each): what,
80
81
 
81
82
  ## Keep the connections
82
83
 
83
- Link related pages with `[[page]]` where a reader would follow the connection, with the reason; do not link every noun. `vibe knowledge find` shows which pages a subject touches. When the wiki has grown or links were edited, run `vibe knowledge lint` and fix what it reports: create or redirect a broken link's target, link an orphan from `index.md` or a related page, merge duplicate pages, split long ones. An unlinked shared page needs nothing: the next session start lists it; link it from a related page where the connection means something.
84
+ Link related pages with `[[page]]` where a reader would follow the connection, with the reason; do not link every noun. `vibe knowledge find` shows which pages a subject touches. Every knowledge turn runs `vibe knowledge lint --fix` first: orphans join `index.md` on lines ending `<!-- vibe:auto -->` (removed when the page goes) and a project page `log.md` links to is created; unmarked lines are only reported. The turn names what is left: redirect or create a broken link's target, merge pages sharing a title, split long pages, fix unknown types. Give an auto line a real description when you can. An unlinked shared page needs nothing: the next session start lists it; link it from a related page where the connection means something.
85
+
86
+ ## Review
87
+
88
+ After enough changed pages (`--review`, `--review-days`), the knowledge turn is also a review turn listing the changed pages and the pages they link. Read those together, not the whole wiki, for what a script cannot see: two pages disagreeing on one subject, a dated claim later facts or the code no longer support, a replaced decision still reading as current. Correct it with a one-line reason, date and source; mark a replaced decision with `supersedes` or `status: archived`; when unsure which claim holds, ask the user in one line. Report it like any knowledge turn ("점검: 모순 1건 수정 · 대체된 결정 1건 표시"), or nothing when everything holds.
84
89
 
85
90
  ## Tell the user
86
91
 
@@ -13,7 +13,7 @@ Read fewer relevant files, each in full (Claude Read; Codex `cat`); grep/rg/find
13
13
  1. Turn the discovery result into scenarios. A scenario is stored only if it carries exactly one check type:
14
14
  - `run` command exit code · `file` exists/regex/contains/absent (a regex that must match nowhere)/traceable (every number is in an evidence file)/a11y (mechanical accessibility defects)/schema/sum (a column total equals a reference) · `http` status/schema/maxMs · `eval` count of matching labelled cases (jsonl `{input, expected}` through a runner's stdin/stdout, `expect.pass` is a count) · `review` an antislop pack's reviewer stages in order (text: copy editor → chief editor; design: markup reviewer → art director; code: reviewer → maintainer), run by the harness, exact `PASS` only · `human` no verdict (goes to the inbox)
15
15
  - When a success condition is human-read text (blog, column, article, report, script, speech), propose `check: { type: review, path: <file>, contract: <file>, evidence: <file> }` and mark it `⚠ model-judged` in the approval message.
16
- - When it is something a person looks at (screen, page, component, dashboard, slide, card news, poster), propose `check: { type: review, pack: design, path: <file or directory>, contract: <brief>, screenshot: <png, if an earlier scenario renders one> }`, same mark.
16
+ - When it is something a person looks at (screen, page, component, dashboard, slide, card news, poster), propose `check: { type: review, pack: design, path: <file or directory>, contract: <brief>, screenshot: <png, if an earlier scenario renders one>, mockup: <confirmed mockup> }`, same mark. A new screen's mockup is confirmed before this step (design guide); list it in `sources` so changing it voids the approval.
17
17
  - When the code will be read or maintained by someone else (a library, a contribution, a handoff), propose `check: { type: review, pack: code, path: <directory>, contract: <brief>, changed: true }`, same mark; `changed: true` reviews what changed against HEAD and its one-hop importers instead of the whole directory (a git ref instead of `true` diffs against it) — propose it on every `review` of a directory in a git repository; otherwise `vibe size` stays the code gate.
18
18
  - Next to a text `review` that has an evidence file, propose `check: { type: file, path: <file>, traceable: <evidence file> }` (every number in the text is in the evidence); next to a design `review` on html, `check: { type: file, path: <html>, a11y: true }` (alt, heading order, unnamed controls, unlabelled inputs, contrast). These check facts; the reviewers keep the judgment.
19
19
  - A check observes. A `run` command that changes state (restore, reset, seed, migrate down, push, deploy) is detected and marked `irreversible`; `check --all` will not run it without `vibe authorize`, and it cannot become a regression. Rewrite such a check to observe — run against a copy, read the result — or declare `irreversible: <action>` and say so in the approval message.