agent-harness-kit 0.10.0 → 0.10.2
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/src/core/upgrade.mjs +11 -0
- package/src/templates/.claude/skills/deliver-html/scripts/wrap-html.mjs +0 -0
- package/src/templates/.claude/skills/inspect-module/scripts/module-summary.mjs +40 -8
- package/src/templates/scripts/session-end.sh.hbs +34 -5
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"source": {
|
|
12
12
|
"source": "github",
|
|
13
13
|
"repo": "tuanle96/agent-harness-kit",
|
|
14
|
-
"ref": "v0.10.
|
|
14
|
+
"ref": "v0.10.2"
|
|
15
15
|
},
|
|
16
|
-
"version": "0.10.
|
|
16
|
+
"version": "0.10.2",
|
|
17
17
|
"description": "Solo-dev harness engineering kit — layered architecture, GC ritual, structural tests, review subagents.",
|
|
18
18
|
"category": "development",
|
|
19
19
|
"keywords": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-harness-kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Solo-dev harness engineering kit — layered architecture, garbage-collection ritual, structural tests, review subagents. Optimized for Claude Code 2.1+.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Tuan Le"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-harness-kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Solo-dev harness engineering kit for Claude Code. Layered architecture, structural tests, garbage-collection ritual, review subagents — without the enterprise overhead.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/core/upgrade.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
pathForStack,
|
|
21
21
|
buildContext,
|
|
22
22
|
mergeHooksIntoSettings,
|
|
23
|
+
mergeStatusLineIntoSettings,
|
|
23
24
|
USER_OWNED_FILES as USER_OWNED_FROM_RENDERER,
|
|
24
25
|
EXEC_BITS,
|
|
25
26
|
SUPPORTED_HUMAN_LANGS,
|
|
@@ -313,6 +314,16 @@ export async function upgrade({ cwd, kitVersion, yes }) {
|
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
|
|
317
|
+
// v0.8 — statusLine injection. Mirrors renderAll's tail (render-templates.mjs:474-480).
|
|
318
|
+
// Idempotent; never clobbers a user-customised statusLine.command pointing elsewhere.
|
|
319
|
+
if (existsSync(resolve(cwd, "scripts/statusline.mjs"))) {
|
|
320
|
+
const sl = await mergeStatusLineIntoSettings(cwd);
|
|
321
|
+
if (sl.changed) {
|
|
322
|
+
lockfile.files[".claude/settings.json"] = sha256(sl.rawContent);
|
|
323
|
+
console.log(pc.dim(` ${pc.green("~")} .claude/settings.json (statusLine merged)`));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
316
327
|
lockfile.version = kitVersion;
|
|
317
328
|
await writeFile(lockPath, JSON.stringify(lockfile, null, 2) + "\n");
|
|
318
329
|
|
|
Binary file
|
|
@@ -83,17 +83,41 @@ function outboundDeps(target) {
|
|
|
83
83
|
return [...out].slice(0, 50);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function inboundDeps(target) {
|
|
86
|
+
function inboundDeps(target, cfg) {
|
|
87
87
|
const relTarget = relative(ROOT, resolve(ROOT, target));
|
|
88
88
|
const name = relTarget.split("/").pop().replace(/\.[a-z]+$/i, "");
|
|
89
89
|
if (!name) return [];
|
|
90
90
|
const seen = new Set();
|
|
91
|
+
const patterns = [];
|
|
92
|
+
|
|
93
|
+
// Standard pattern: import/from/require referencing the directory name.
|
|
94
|
+
// Works for TS/JS/Python where the import path mirrors the dir name.
|
|
95
|
+
patterns.push(
|
|
96
|
+
new RegExp(`(import|from|require\\().*['"][^'"]*${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Rust workspace pattern: when domain has `useIdentPattern` (e.g.
|
|
100
|
+
// "unibot_{layer}"), the crate is `use`d under its layer-derived ident
|
|
101
|
+
// — NOT the dir name. For `crates/unibot-types/` with pattern
|
|
102
|
+
// "unibot_{layer}", we should also match `use unibot_types::`. Without
|
|
103
|
+
// this branch the inbound list silently misses every workspace caller.
|
|
104
|
+
const layerInfo = whichLayer(target, cfg);
|
|
105
|
+
if (layerInfo) {
|
|
106
|
+
const domain = (cfg?.domains || []).find((d) => (d.name || "default") === layerInfo.domain);
|
|
107
|
+
if (domain?.useIdentPattern) {
|
|
108
|
+
const ident = domain.useIdentPattern.replace("{layer}", layerInfo.layer);
|
|
109
|
+
const escaped = ident.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
110
|
+
patterns.push(new RegExp(`\\b(?:pub\\s+)?use\\s+${escaped}\\b`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
// Search the whole project root for references back to the target
|
|
92
115
|
// module. Filter out self-references.
|
|
93
|
-
const re
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
for (const re of patterns) {
|
|
117
|
+
for (const line of scan(".", re)) {
|
|
118
|
+
const m = line.match(/^([^:]+):\d+:/);
|
|
119
|
+
if (m && m[1] !== relTarget && !m[1].startsWith(`${relTarget}/`)) seen.add(m[1]);
|
|
120
|
+
}
|
|
97
121
|
}
|
|
98
122
|
return [...seen].slice(0, 30);
|
|
99
123
|
}
|
|
@@ -103,14 +127,22 @@ function readLayers() {
|
|
|
103
127
|
catch { return null; }
|
|
104
128
|
}
|
|
105
129
|
|
|
130
|
+
// Resolve the layer for a module path. Honors `layerDirPattern` on the
|
|
131
|
+
// domain so workspaces that prefix layer directories (e.g. Rust workspace
|
|
132
|
+
// `crates/unibot-types/` with `layerDirPattern: "unibot-{layer}"`) match
|
|
133
|
+
// correctly. Without this, paths with custom prefixes would silently fail
|
|
134
|
+
// to match and return layer:null — the bug that ships /inspect-module's
|
|
135
|
+
// most useful columns blank.
|
|
106
136
|
function whichLayer(target, cfg) {
|
|
107
137
|
if (!cfg?.domains) return null;
|
|
108
138
|
const rel = relative(ROOT, resolve(ROOT, target));
|
|
109
139
|
for (const d of cfg.domains) {
|
|
110
140
|
if (!d?.layers || !d.root) continue;
|
|
141
|
+
const pattern = d.layerDirPattern || "{layer}";
|
|
111
142
|
for (const layer of d.layers) {
|
|
112
|
-
const
|
|
113
|
-
|
|
143
|
+
const dirName = pattern.replace("{layer}", layer);
|
|
144
|
+
const prefix = `${d.root}/${dirName}/`;
|
|
145
|
+
if (rel.startsWith(prefix) || rel === `${d.root}/${dirName}`) {
|
|
114
146
|
return { domain: d.name || "default", layer };
|
|
115
147
|
}
|
|
116
148
|
}
|
|
@@ -135,7 +167,7 @@ function main() {
|
|
|
135
167
|
layer: whichLayer(target, cfg),
|
|
136
168
|
exports: listExports(target),
|
|
137
169
|
outbound: outboundDeps(target),
|
|
138
|
-
inbound: inboundDeps(target),
|
|
170
|
+
inbound: inboundDeps(target, cfg),
|
|
139
171
|
recent: recentCommits(target),
|
|
140
172
|
};
|
|
141
173
|
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
# Claude Code docs).
|
|
5
5
|
#
|
|
6
6
|
# Output line shape:
|
|
7
|
-
# YYYY-MM-DD HH:MM | session_end | <reason> | <branch> | <sha>
|
|
7
|
+
# YYYY-MM-DD HH:MM | session_end | <reason> | <branch> | <sha> | <session_id>
|
|
8
8
|
#
|
|
9
9
|
# Example:
|
|
10
|
-
# 2026-05-16 19:00 | session_end | clear | main | abc1234
|
|
10
|
+
# 2026-05-16 19:00 | session_end | clear | main | abc1234 | sess_abc123
|
|
11
11
|
#
|
|
12
12
|
# Reasons (per Claude Code docs): clear, resume, logout, prompt_input_exit,
|
|
13
13
|
# bypass_permissions_disabled, other.
|
|
14
|
+
#
|
|
15
|
+
# Dedup: a line is only appended when (session_id, reason) differs from the
|
|
16
|
+
# most recent matching entry in PROGRESS.md. Prevents the duplicate-spam
|
|
17
|
+
# bug where Claude Code fires SessionEnd more than once on a single teardown.
|
|
14
18
|
set -eo pipefail
|
|
15
19
|
|
|
16
20
|
INPUT=$(cat)
|
|
@@ -30,10 +34,21 @@ jp() {
|
|
|
30
34
|
fi
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
REASON="
|
|
37
|
+
REASON=""
|
|
38
|
+
SESSION_ID=""
|
|
34
39
|
if have_jp; then
|
|
35
|
-
|
|
40
|
+
# Fallback chain: prefer .end_reason (current Claude Code key), accept
|
|
41
|
+
# .reason as a legacy synonym. Done as two separate jp calls because the
|
|
42
|
+
# Node-fallback (json-pick.mjs) only supports a single `// default` per
|
|
43
|
+
# expression — chaining `// .reason //` would parse-fail there.
|
|
44
|
+
REASON=$(echo "$INPUT" | jp '.end_reason // ""' 2>/dev/null || echo "")
|
|
45
|
+
if [ -z "$REASON" ] || [ "$REASON" = "null" ]; then
|
|
46
|
+
REASON=$(echo "$INPUT" | jp '.reason // ""' 2>/dev/null || echo "")
|
|
47
|
+
fi
|
|
48
|
+
SESSION_ID=$(echo "$INPUT" | jp '.session_id // ""' 2>/dev/null || echo "")
|
|
49
|
+
[ "$SESSION_ID" = "null" ] && SESSION_ID=""
|
|
36
50
|
fi
|
|
51
|
+
[ -z "$REASON" ] || [ "$REASON" = "null" ] && REASON="unknown"
|
|
37
52
|
|
|
38
53
|
BR="(no-git)"
|
|
39
54
|
SHA="(no-git)"
|
|
@@ -44,7 +59,21 @@ fi
|
|
|
44
59
|
|
|
45
60
|
mkdir -p .harness
|
|
46
61
|
TS=$(date +"%Y-%m-%d %H:%M")
|
|
47
|
-
|
|
62
|
+
LINE="$TS | session_end | $REASON | $BR | $SHA | $SESSION_ID"
|
|
63
|
+
|
|
64
|
+
# Idempotency guard: when the SessionEnd hook fires twice on the same
|
|
65
|
+
# teardown (Claude Code sometimes emits both `clear` and a follow-up
|
|
66
|
+
# `prompt_input_exit`, or repeats the same reason), drop the duplicate
|
|
67
|
+
# rather than spamming PROGRESS.md. Match on (session_id, reason): if
|
|
68
|
+
# both are present in the most recent entry for this session, skip.
|
|
69
|
+
DEDUP_KEY="| $REASON | $BR | $SHA | $SESSION_ID"
|
|
70
|
+
if [ -f .harness/PROGRESS.md ] && \
|
|
71
|
+
[ -n "$SESSION_ID" ] && \
|
|
72
|
+
tail -n 5 .harness/PROGRESS.md 2>/dev/null | grep -qF "$DEDUP_KEY"; then
|
|
73
|
+
: # duplicate within the last 5 entries — skip silently
|
|
74
|
+
else
|
|
75
|
+
echo "$LINE" >> .harness/PROGRESS.md
|
|
76
|
+
fi
|
|
48
77
|
|
|
49
78
|
# Rollup side-car — writes a JSONL record to .harness/telemetry.jsonl.
|
|
50
79
|
# Best-effort: never blocks the cleanup-only SessionEnd contract.
|