qualia-framework 6.2.10 → 6.4.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.
Files changed (78) hide show
  1. package/AGENTS.md +8 -7
  2. package/CLAUDE.md +5 -4
  3. package/README.md +27 -56
  4. package/bin/cli.js +113 -18
  5. package/bin/command-surface.js +75 -0
  6. package/bin/harness-eval.js +296 -0
  7. package/bin/install.js +43 -31
  8. package/bin/knowledge-flush.js +21 -10
  9. package/bin/knowledge.js +1 -1
  10. package/bin/learning-candidates.js +217 -0
  11. package/bin/project-snapshot.js +20 -0
  12. package/bin/prune-deprecated.js +64 -0
  13. package/bin/report-payload.js +18 -0
  14. package/bin/runtime-manifest.js +7 -0
  15. package/bin/security-scan.js +409 -0
  16. package/bin/state.js +31 -0
  17. package/bin/status-snapshot.js +363 -0
  18. package/bin/trust-score.js +3 -11
  19. package/bin/work-packet.js +228 -0
  20. package/docs/erp-contract.md +81 -1
  21. package/docs/onboarding.html +0 -11
  22. package/guide.md +15 -38
  23. package/hooks/fawzi-approval-guard.js +143 -0
  24. package/hooks/pre-compact.js +232 -0
  25. package/hooks/pre-deploy-gate.js +74 -1
  26. package/hooks/session-start.js +29 -1
  27. package/package.json +1 -1
  28. package/qualia-design/frontend.md +2 -2
  29. package/rules/codex-goal.md +1 -1
  30. package/rules/one-opinion.md +2 -2
  31. package/rules/speed.md +0 -1
  32. package/skills/qualia/SKILL.md +4 -4
  33. package/skills/qualia-build/SKILL.md +1 -1
  34. package/skills/qualia-discuss/SKILL.md +1 -1
  35. package/skills/qualia-doctor/SKILL.md +1 -1
  36. package/skills/qualia-feature/SKILL.md +2 -2
  37. package/skills/qualia-fix/SKILL.md +4 -4
  38. package/skills/qualia-idk/SKILL.md +133 -54
  39. package/skills/qualia-learn/SKILL.md +2 -2
  40. package/skills/qualia-map/SKILL.md +1 -1
  41. package/skills/qualia-milestone/SKILL.md +1 -1
  42. package/skills/qualia-new/SKILL.md +1 -1
  43. package/skills/qualia-optimize/SKILL.md +1 -1
  44. package/skills/qualia-plan/SKILL.md +1 -1
  45. package/skills/qualia-polish/REFERENCE.md +1 -1
  46. package/skills/qualia-polish/SKILL.md +19 -4
  47. package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +4 -4
  48. package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
  49. package/skills/qualia-postmortem/SKILL.md +1 -1
  50. package/skills/qualia-report/SKILL.md +1 -1
  51. package/skills/qualia-research/SKILL.md +1 -1
  52. package/skills/qualia-review/SKILL.md +1 -1
  53. package/skills/qualia-road/SKILL.md +15 -20
  54. package/skills/qualia-secure/SKILL.md +105 -0
  55. package/skills/qualia-ship/SKILL.md +12 -5
  56. package/skills/qualia-test/SKILL.md +1 -1
  57. package/skills/qualia-verify/SKILL.md +10 -2
  58. package/skills/zoho-workflow/SKILL.md +1 -1
  59. package/templates/help.html +1 -12
  60. package/tests/bin.test.sh +147 -75
  61. package/tests/hooks.test.sh +81 -1
  62. package/tests/install-smoke.test.sh +14 -4
  63. package/tests/lib.test.sh +145 -3
  64. package/tests/published-install-smoke.test.sh +5 -4
  65. package/tests/refs.test.sh +32 -20
  66. package/tests/runner.js +30 -29
  67. package/tests/state.test.sh +106 -7
  68. package/skills/qualia-debug/SKILL.md +0 -193
  69. package/skills/qualia-flush/SKILL.md +0 -198
  70. package/skills/qualia-help/SKILL.md +0 -74
  71. package/skills/qualia-hook-gen/SKILL.md +0 -206
  72. package/skills/qualia-issues/SKILL.md +0 -151
  73. package/skills/qualia-pause/SKILL.md +0 -68
  74. package/skills/qualia-resume/SKILL.md +0 -52
  75. package/skills/qualia-skill-new/SKILL.md +0 -173
  76. package/skills/qualia-triage/SKILL.md +0 -152
  77. package/skills/qualia-vibe/SKILL.md +0 -229
  78. package/skills/qualia-zoom/SKILL.md +0 -51
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ // bin/learning-candidates.js — scan recent git history + daily-log for
3
+ // repeated patterns and emit a list of skill-creation candidates.
4
+ //
5
+ // Run via:
6
+ // qualia-framework learn-scan # writes ~/.claude/knowledge/learning-candidates.md
7
+ // qualia-framework learn-scan --print # also prints to stdout
8
+ // qualia-framework learn-scan --since=7 # look at last 7 days (default: 14)
9
+ //
10
+ // What it detects:
11
+ // 1. Repeated fix-scope patterns — "fix(auth):" appearing 3+ times in 14 days
12
+ // → suggest a skill or hook that prevents/diagnoses that class of bug.
13
+ // 2. Repeated touched-file patterns — same file appearing in 4+ session
14
+ // checkpoints → suggest factoring or codifying the workflow around it.
15
+ // 3. Repeated phrases in daily-log entries — heuristic, low-signal v1.
16
+ //
17
+ // What it does NOT do:
18
+ // - Auto-create skills. That's /qualia-skill-new's job (with human review).
19
+ // - Touch the wiki tier (knowledge/concepts/). That's /qualia-flush's job.
20
+ // - Run continuously in background. ECC's Haiku observer is over-engineered
21
+ // for Qualia's weekly cadence. Run this manually or via /qualia-flush.
22
+
23
+ const fs = require("fs");
24
+ const path = require("path");
25
+ const os = require("os");
26
+ const { spawnSync } = require("child_process");
27
+
28
+ function qualiaHome() {
29
+ if (process.env.QUALIA_HOME) return process.env.QUALIA_HOME;
30
+ const parent = path.basename(path.dirname(__dirname));
31
+ if (parent === ".codex" || parent === ".claude") return path.dirname(__dirname);
32
+ return path.join(os.homedir(), ".claude");
33
+ }
34
+
35
+ function git(args, opts = {}) {
36
+ try {
37
+ const r = spawnSync("git", args, { encoding: "utf8", timeout: 3000, shell: process.platform === "win32", ...opts });
38
+ if (r.status !== 0) return "";
39
+ return (r.stdout || "").trim();
40
+ } catch {
41
+ return "";
42
+ }
43
+ }
44
+
45
+ function parseArgs(argv) {
46
+ const args = { print: false, sinceDays: 14 };
47
+ for (let i = 0; i < argv.length; i++) {
48
+ const a = argv[i];
49
+ if (a === "--print") args.print = true;
50
+ else if (a.startsWith("--since=")) {
51
+ const n = parseInt(a.split("=")[1], 10);
52
+ if (n > 0) args.sinceDays = n;
53
+ } else if (a === "--since" && argv[i + 1]) {
54
+ const n = parseInt(argv[++i], 10);
55
+ if (n > 0) args.sinceDays = n;
56
+ }
57
+ }
58
+ return args;
59
+ }
60
+
61
+ // Conventional Commits subject parser. Returns {type, scope} or null.
62
+ function parseCommitSubject(subject) {
63
+ const m = subject.match(/^([a-z]+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/i);
64
+ if (!m) return null;
65
+ return { type: m[1].toLowerCase(), scope: m[2] || "", description: m[4] };
66
+ }
67
+
68
+ function scanRepoCommits(repoRoot, sinceDays) {
69
+ const out = git(["log", `--since=${sinceDays}.days`, "--pretty=%H%x09%s"], { cwd: repoRoot });
70
+ if (!out) return [];
71
+ const commits = [];
72
+ for (const line of out.split("\n")) {
73
+ const [sha, subject] = line.split("\t");
74
+ if (!sha || !subject) continue;
75
+ const parsed = parseCommitSubject(subject);
76
+ if (parsed) commits.push({ sha, subject, ...parsed });
77
+ }
78
+ return commits;
79
+ }
80
+
81
+ function aggregateFixPatterns(commits) {
82
+ // Group fix-type commits by scope (or by first-word of description if no scope).
83
+ const groups = new Map();
84
+ for (const c of commits) {
85
+ if (c.type !== "fix") continue;
86
+ const key = c.scope || c.description.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "");
87
+ if (!key) continue;
88
+ const arr = groups.get(key) || [];
89
+ arr.push(c);
90
+ groups.set(key, arr);
91
+ }
92
+ return [...groups.entries()]
93
+ .filter(([, arr]) => arr.length >= 3)
94
+ .sort((a, b) => b[1].length - a[1].length)
95
+ .map(([scope, arr]) => ({ scope, count: arr.length, samples: arr.slice(0, 3).map((c) => `${c.sha.slice(0, 7)} ${c.subject}`) }));
96
+ }
97
+
98
+ function readDailyLogs(qhome, sinceDays) {
99
+ const dir = path.join(qhome, "knowledge", "daily-log");
100
+ if (!fs.existsSync(dir)) return [];
101
+ const cutoff = Date.now() - sinceDays * 86_400_000;
102
+ const out = [];
103
+ for (const f of fs.readdirSync(dir)) {
104
+ if (!f.endsWith(".md")) continue;
105
+ const m = f.match(/^(\d{4}-\d{2}-\d{2})\.md$/);
106
+ if (!m) continue;
107
+ const date = new Date(m[1] + "T00:00:00Z").getTime();
108
+ if (date < cutoff) continue;
109
+ try {
110
+ out.push({ date: m[1], content: fs.readFileSync(path.join(dir, f), "utf8") });
111
+ } catch {}
112
+ }
113
+ return out;
114
+ }
115
+
116
+ function aggregateTouchedFiles(dailyLogs) {
117
+ // Look for "touched=a,b,c" across entries. Count occurrences per file.
118
+ const counts = new Map();
119
+ for (const log of dailyLogs) {
120
+ const matches = log.content.matchAll(/touched=([^\s·]+)/g);
121
+ for (const m of matches) {
122
+ for (const f of m[1].split(",")) {
123
+ const k = f.trim();
124
+ if (!k) continue;
125
+ counts.set(k, (counts.get(k) || 0) + 1);
126
+ }
127
+ }
128
+ }
129
+ return [...counts.entries()]
130
+ .filter(([, n]) => n >= 4)
131
+ .sort((a, b) => b[1] - a[1])
132
+ .slice(0, 10)
133
+ .map(([file, count]) => ({ file, count }));
134
+ }
135
+
136
+ function render({ sinceDays, fixPatterns, touchedFiles, repoRoot }) {
137
+ const lines = [];
138
+ lines.push(`# Learning candidates — generated ${new Date().toISOString()}`);
139
+ lines.push("");
140
+ lines.push(`Scope: last ${sinceDays} days.`);
141
+ if (repoRoot) lines.push(`Repo: ${repoRoot}`);
142
+ lines.push("");
143
+ lines.push("Each candidate below is a pattern that recurred enough times to be worth promoting into a skill, agent, or hook. Review and act:");
144
+ lines.push("- `/qualia-skill-new` — create a Qualia skill for the workflow");
145
+ lines.push("- `/qualia-hook-gen` — convert a CLAUDE.md instruction into a deterministic hook");
146
+ lines.push("- `/qualia-learn` — save a one-off learning to the knowledge wiki");
147
+ lines.push("- Ignore — patterns that aren't worth automating");
148
+ lines.push("");
149
+
150
+ lines.push("## Repeated fix-scopes (Conventional Commits)");
151
+ if (fixPatterns.length === 0) {
152
+ lines.push("(none — no scope has 3+ fixes in the window)");
153
+ } else {
154
+ for (const fp of fixPatterns) {
155
+ lines.push(`### \`fix(${fp.scope})\` — ${fp.count} commits`);
156
+ lines.push("Recent samples:");
157
+ for (const s of fp.samples) lines.push(`- ${s}`);
158
+ lines.push("");
159
+ lines.push(`> **Suggested action:** if this keeps recurring, add a guard hook or a /qualia-skill-new dedicated to debugging \`${fp.scope}\`.`);
160
+ lines.push("");
161
+ }
162
+ }
163
+
164
+ lines.push("## Repeatedly-touched files");
165
+ if (touchedFiles.length === 0) {
166
+ lines.push("(none — no file appeared in 4+ session checkpoints)");
167
+ } else {
168
+ lines.push("File | Touched count");
169
+ lines.push("---|---:");
170
+ for (const t of touchedFiles) lines.push(`\`${t.file}\` | ${t.count}`);
171
+ lines.push("");
172
+ lines.push("> **Suggested action:** files touched this often may be a hotspot (deserves more tests) or a friction point (deserves a skill that automates the recurring edit).");
173
+ }
174
+ lines.push("");
175
+ lines.push("---");
176
+ lines.push("_Generated by `bin/learning-candidates.js`. Re-run with `qualia-framework learn-scan` or `qualia-framework learn-scan --since=30`._");
177
+
178
+ return lines.join("\n") + "\n";
179
+ }
180
+
181
+ function main() {
182
+ const args = parseArgs(process.argv.slice(2));
183
+ const qhome = qualiaHome();
184
+ const cwd = process.cwd();
185
+ const repoRoot = git(["rev-parse", "--show-toplevel"], { cwd }) || "";
186
+
187
+ const commits = repoRoot ? scanRepoCommits(repoRoot, args.sinceDays) : [];
188
+ const fixPatterns = aggregateFixPatterns(commits);
189
+ const dailyLogs = readDailyLogs(qhome, args.sinceDays);
190
+ const touchedFiles = aggregateTouchedFiles(dailyLogs);
191
+
192
+ const doc = render({ sinceDays: args.sinceDays, fixPatterns, touchedFiles, repoRoot });
193
+
194
+ const outDir = path.join(qhome, "knowledge");
195
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
196
+ const outPath = path.join(outDir, "learning-candidates.md");
197
+ fs.writeFileSync(outPath, doc);
198
+
199
+ // Stamp last-scan time.
200
+ fs.writeFileSync(path.join(qhome, ".qualia-last-learning-scan"), String(Date.now()));
201
+
202
+ if (args.print) {
203
+ process.stdout.write(doc);
204
+ } else {
205
+ console.log(`Wrote ${fixPatterns.length} fix-pattern candidate(s) + ${touchedFiles.length} hot file(s) to ${outPath}`);
206
+ }
207
+ }
208
+
209
+ module.exports = { main, aggregateFixPatterns, aggregateTouchedFiles, parseCommitSubject };
210
+
211
+ if (require.main === module) {
212
+ try { main(); }
213
+ catch (e) {
214
+ console.error(`learn-scan failed: ${e.message}`);
215
+ process.exit(1);
216
+ }
217
+ }
@@ -5,6 +5,8 @@ const https = require("https");
5
5
  const os = require("os");
6
6
  const path = require("path");
7
7
  const { spawnSync } = require("child_process");
8
+ const harnessEval = require("./harness-eval.js");
9
+ const { readLocalWorkPacket } = require("./work-packet.js");
8
10
 
9
11
  function readJson(file, fallback = {}) {
10
12
  try {
@@ -90,6 +92,8 @@ function buildSnapshot(options = {}) {
90
92
  const projectId = tracking.project_id || repoSlug(gitRemote) || path.basename(cwd);
91
93
  const currentMilestone = Number(tracking.milestone || 1);
92
94
  const currentPhase = Number(tracking.phase || 0);
95
+ const latestHarnessEval = harnessEval.latestEval(cwd);
96
+ const workPacket = readLocalWorkPacket(cwd);
93
97
  const totalPhases = Number(tracking.total_phases || 0);
94
98
  const lifetime = tracking.lifetime && typeof tracking.lifetime === "object" ? tracking.lifetime : {};
95
99
  const closedMilestones = Array.isArray(tracking.milestones) ? tracking.milestones : [];
@@ -118,6 +122,11 @@ function buildSnapshot(options = {}) {
118
122
  ...(uuid(tracking.erp_project_id) ? { erp_project_id: uuid(tracking.erp_project_id) } : {}),
119
123
  ...(uuid(tracking.client_id) ? { client_id: uuid(tracking.client_id) } : {}),
120
124
  ...(uuid(tracking.workspace_id) ? { workspace_id: uuid(tracking.workspace_id) } : {}),
125
+ ...(workPacket ? { work_packet_id: workPacket.id } : {}),
126
+ ...(workPacket && workPacket.assignment_id ? { assignment_id: workPacket.assignment_id } : {}),
127
+ ...(workPacket && workPacket.deadline_date
128
+ ? { assignment_deadline: workPacket.deadline_date }
129
+ : {}),
121
130
  },
122
131
  project: {
123
132
  name: tracking.project || path.basename(cwd),
@@ -137,6 +146,17 @@ function buildSnapshot(options = {}) {
137
146
  verification: tracking.verification || "pending",
138
147
  gap_cycles: (tracking.gap_cycles || {})[String(currentPhase)] || 0,
139
148
  },
149
+ quality: {
150
+ harness_eval: latestHarnessEval
151
+ ? {
152
+ status: latestHarnessEval.status,
153
+ score: latestHarnessEval.score,
154
+ phase: latestHarnessEval.phase,
155
+ generated_at: latestHarnessEval.generated_at,
156
+ artifact: latestHarnessEval.artifacts && latestHarnessEval.artifacts.json,
157
+ }
158
+ : null,
159
+ },
140
160
  journey: {
141
161
  total_milestones: journeyTotal,
142
162
  milestones: journey.map((milestone) => ({
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ // Shared helper: prune retired skill folders from an install home (~/.claude or ~/.codex).
3
+ //
4
+ // Why a separate file: install.js already runs prune on install, but users often run
5
+ // `qualia-framework doctor` more frequently than they re-install. When a skill is
6
+ // retired (moved from ACTIVE_SKILLS → RETIRED_SKILLS in command-surface.js), the
7
+ // folder must also be removed from the install home, or the harness will keep
8
+ // advertising it as live and users will invoke "trap skills" that no longer route.
9
+ //
10
+ // Both install.js and cli.js (doctor) call this.
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+ const { RETIRED_SKILLS } = require("./command-surface.js");
15
+
16
+ function findGhostSkills(baseDir) {
17
+ const skillsDir = path.join(baseDir, "skills");
18
+ if (!fs.existsSync(skillsDir)) return [];
19
+ return RETIRED_SKILLS.filter((name) => fs.existsSync(path.join(skillsDir, name)));
20
+ }
21
+
22
+ // Returns { removed: [names], errors: [{name, target, error}] }.
23
+ // Callers can decide how to surface errors — the doctor flow logs them, the
24
+ // install flow shows them as warnings. We do NOT silently swallow them (per
25
+ // CodeRabbit review on PR #46): a permissions / mount-point failure during
26
+ // prune leaves stale ghost skills installed and the user has no signal.
27
+ function pruneGhostSkills(baseDir) {
28
+ const skillsDir = path.join(baseDir, "skills");
29
+ if (!fs.existsSync(skillsDir)) return { removed: [], errors: [] };
30
+ const removed = [];
31
+ const errors = [];
32
+ for (const name of RETIRED_SKILLS) {
33
+ const target = path.join(skillsDir, name);
34
+ try {
35
+ if (fs.existsSync(target)) {
36
+ fs.rmSync(target, { recursive: true, force: true });
37
+ removed.push(name);
38
+ }
39
+ } catch (err) {
40
+ errors.push({ name, target, error: err && err.message ? err.message : String(err) });
41
+ }
42
+ }
43
+ return { removed, errors };
44
+ }
45
+
46
+ module.exports = { findGhostSkills, pruneGhostSkills };
47
+
48
+ if (require.main === module) {
49
+ // CLI: `node prune-deprecated.js <baseDir>` — useful for manual cleanup.
50
+ const baseDir = process.argv[2] || path.join(require("os").homedir(), ".claude");
51
+ const { removed, errors } = pruneGhostSkills(baseDir);
52
+ if (errors.length > 0) {
53
+ console.error(`Pruned ${removed.length} ghost skill(s); ${errors.length} failed:`);
54
+ for (const e of errors) console.error(` ✗ ${e.name} (${e.target}): ${e.error}`);
55
+ if (removed.length > 0) for (const name of removed) console.log(` - ${name}`);
56
+ process.exit(1);
57
+ }
58
+ if (removed.length === 0) {
59
+ console.log(`No ghost skills in ${baseDir}/skills/`);
60
+ } else {
61
+ console.log(`Pruned ${removed.length} ghost skill(s) from ${baseDir}/skills/:`);
62
+ for (const name of removed) console.log(` - ${name}`);
63
+ }
64
+ }
@@ -3,6 +3,8 @@ const fs = require("fs");
3
3
  const os = require("os");
4
4
  const path = require("path");
5
5
  const { spawnSync } = require("child_process");
6
+ const harnessEval = require("./harness-eval.js");
7
+ const { readLocalWorkPacket } = require("./work-packet.js");
6
8
 
7
9
  function readJson(file, fallback = {}) {
8
10
  try {
@@ -84,6 +86,8 @@ function buildPayload(options = {}) {
84
86
  const gitRemote = tracking.git_remote || git(["config", "--get", "remote.origin.url"], cwd);
85
87
  const projectKey = tracking.project_id || repoSlug(gitRemote) || path.basename(cwd);
86
88
  const phase = tracking.phase;
89
+ const latestHarnessEval = harnessEval.latestEval(cwd);
90
+ const workPacket = readLocalWorkPacket(cwd);
87
91
 
88
92
  return {
89
93
  project: tracking.project || path.basename(cwd),
@@ -93,6 +97,11 @@ function buildPayload(options = {}) {
93
97
  ...(uuid(tracking.erp_project_id) ? { erp_project_id: uuid(tracking.erp_project_id) } : {}),
94
98
  ...(uuid(tracking.client_id) ? { client_id: uuid(tracking.client_id) } : {}),
95
99
  ...(uuid(tracking.workspace_id) ? { workspace_id: uuid(tracking.workspace_id) } : {}),
100
+ ...(workPacket ? { work_packet_id: workPacket.id } : {}),
101
+ ...(workPacket && workPacket.assignment_id ? { assignment_id: workPacket.assignment_id } : {}),
102
+ ...(workPacket && workPacket.deadline_date
103
+ ? { assignment_deadline: workPacket.deadline_date }
104
+ : {}),
96
105
  client: tracking.client || "",
97
106
  client_report_id: env.CLIENT_REPORT_ID || "",
98
107
  framework_version: config.version || "",
@@ -114,6 +123,15 @@ function buildPayload(options = {}) {
114
123
  ...(tracking.last_pushed_at ? { last_pushed_at: tracking.last_pushed_at } : {}),
115
124
  session_duration_minutes: sessionDurationMinutes(tracking.session_started_at, submittedAt),
116
125
  lifetime: tracking.lifetime || {},
126
+ ...(latestHarnessEval ? {
127
+ harness_eval: {
128
+ status: latestHarnessEval.status,
129
+ score: latestHarnessEval.score,
130
+ phase: latestHarnessEval.phase,
131
+ generated_at: latestHarnessEval.generated_at,
132
+ artifact: latestHarnessEval.artifacts && latestHarnessEval.artifacts.json,
133
+ },
134
+ } : {}),
117
135
  commits: recentCommitHashes(cwd),
118
136
  notes,
119
137
  submitted_by: env.SUBMITTED_BY || "unknown",
@@ -3,6 +3,7 @@
3
3
 
4
4
  const RUNTIME_BIN_SCRIPTS = [
5
5
  { file: "runtime-manifest.js", label: "runtime-manifest.js (shared install manifest)" },
6
+ { file: "command-surface.js", label: "command-surface.js (active/deprecated skill manifest)" },
6
7
  { file: "host-adapters.js", label: "host-adapters.js (Claude/Codex path renderer)" },
7
8
  { file: "state.js", label: "state.js (state machine)" },
8
9
  { file: "qualia-ui.js", label: "qualia-ui.js (cosmetics library)" },
@@ -15,11 +16,17 @@ const RUNTIME_BIN_SCRIPTS = [
15
16
  { file: "agent-runs.js", label: "agent-runs.js (agent telemetry writer)" },
16
17
  { file: "slop-detect.mjs", label: "slop-detect.mjs (anti-pattern scanner — runs pre-commit on frontend builds)" },
17
18
  { file: "erp-retry.js", label: "erp-retry.js (ERP report retry queue — drained by session-start hook and erp-flush CLI)" },
19
+ { file: "work-packet.js", label: "work-packet.js (ERP mission/work packet pull + local reader)" },
18
20
  { file: "report-payload.js", label: "report-payload.js (Framework -> ERP report payload builder)" },
19
21
  { file: "project-snapshot.js", label: "project-snapshot.js (ERP/admin project progress snapshot)" },
20
22
  { file: "trust-score.js", label: "trust-score.js (harness health scoring)" },
23
+ { file: "harness-eval.js", label: "harness-eval.js (project eval scoring + evidence artifact)" },
21
24
  { file: "codex-goal.js", label: "codex-goal.js (Codex /goal objective + token-budget suggester)" },
22
25
  { file: "planning-hygiene.js", label: "planning-hygiene.js (.planning organization scanner)" },
26
+ { file: "prune-deprecated.js", label: "prune-deprecated.js (ghost-skill cleanup for retired commands)" },
27
+ { file: "learning-candidates.js", label: "learning-candidates.js (scan recent commits + daily-log for patterns worth promoting)" },
28
+ { file: "status-snapshot.js", label: "status-snapshot.js (portable operator snapshot — install + project + work + ERP + memory)" },
29
+ { file: "security-scan.js", label: "security-scan.js (static security scanner for agent config — secrets, permissions, hook hygiene)" },
23
30
  ];
24
31
 
25
32
  function binFiles() {