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.
- package/AGENTS.md +8 -7
- package/CLAUDE.md +5 -4
- package/README.md +27 -56
- package/bin/cli.js +113 -18
- package/bin/command-surface.js +75 -0
- package/bin/harness-eval.js +296 -0
- package/bin/install.js +43 -31
- package/bin/knowledge-flush.js +21 -10
- package/bin/knowledge.js +1 -1
- package/bin/learning-candidates.js +217 -0
- package/bin/project-snapshot.js +20 -0
- package/bin/prune-deprecated.js +64 -0
- package/bin/report-payload.js +18 -0
- package/bin/runtime-manifest.js +7 -0
- package/bin/security-scan.js +409 -0
- package/bin/state.js +31 -0
- package/bin/status-snapshot.js +363 -0
- package/bin/trust-score.js +3 -11
- package/bin/work-packet.js +228 -0
- package/docs/erp-contract.md +81 -1
- package/docs/onboarding.html +0 -11
- package/guide.md +15 -38
- package/hooks/fawzi-approval-guard.js +143 -0
- package/hooks/pre-compact.js +232 -0
- package/hooks/pre-deploy-gate.js +74 -1
- package/hooks/session-start.js +29 -1
- package/package.json +1 -1
- package/qualia-design/frontend.md +2 -2
- package/rules/codex-goal.md +1 -1
- package/rules/one-opinion.md +2 -2
- package/rules/speed.md +0 -1
- package/skills/qualia/SKILL.md +4 -4
- package/skills/qualia-build/SKILL.md +1 -1
- package/skills/qualia-discuss/SKILL.md +1 -1
- package/skills/qualia-doctor/SKILL.md +1 -1
- package/skills/qualia-feature/SKILL.md +2 -2
- package/skills/qualia-fix/SKILL.md +4 -4
- package/skills/qualia-idk/SKILL.md +133 -54
- package/skills/qualia-learn/SKILL.md +2 -2
- package/skills/qualia-map/SKILL.md +1 -1
- package/skills/qualia-milestone/SKILL.md +1 -1
- package/skills/qualia-new/SKILL.md +1 -1
- package/skills/qualia-optimize/SKILL.md +1 -1
- package/skills/qualia-plan/SKILL.md +1 -1
- package/skills/qualia-polish/REFERENCE.md +1 -1
- package/skills/qualia-polish/SKILL.md +19 -4
- package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +4 -4
- package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
- package/skills/qualia-postmortem/SKILL.md +1 -1
- package/skills/qualia-report/SKILL.md +1 -1
- package/skills/qualia-research/SKILL.md +1 -1
- package/skills/qualia-review/SKILL.md +1 -1
- package/skills/qualia-road/SKILL.md +15 -20
- package/skills/qualia-secure/SKILL.md +105 -0
- package/skills/qualia-ship/SKILL.md +12 -5
- package/skills/qualia-test/SKILL.md +1 -1
- package/skills/qualia-verify/SKILL.md +10 -2
- package/skills/zoho-workflow/SKILL.md +1 -1
- package/templates/help.html +1 -12
- package/tests/bin.test.sh +147 -75
- package/tests/hooks.test.sh +81 -1
- package/tests/install-smoke.test.sh +14 -4
- package/tests/lib.test.sh +145 -3
- package/tests/published-install-smoke.test.sh +5 -4
- package/tests/refs.test.sh +32 -20
- package/tests/runner.js +30 -29
- package/tests/state.test.sh +106 -7
- package/skills/qualia-debug/SKILL.md +0 -193
- package/skills/qualia-flush/SKILL.md +0 -198
- package/skills/qualia-help/SKILL.md +0 -74
- package/skills/qualia-hook-gen/SKILL.md +0 -206
- package/skills/qualia-issues/SKILL.md +0 -151
- package/skills/qualia-pause/SKILL.md +0 -68
- package/skills/qualia-resume/SKILL.md +0 -52
- package/skills/qualia-skill-new/SKILL.md +0 -173
- package/skills/qualia-triage/SKILL.md +0 -152
- package/skills/qualia-vibe/SKILL.md +0 -229
- 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
|
+
}
|
package/bin/project-snapshot.js
CHANGED
|
@@ -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
|
+
}
|
package/bin/report-payload.js
CHANGED
|
@@ -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",
|
package/bin/runtime-manifest.js
CHANGED
|
@@ -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() {
|