qualia-framework 5.9.1 → 6.2.7
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 +2 -1
- package/CLAUDE.md +2 -1
- package/README.md +45 -29
- package/agents/builder.md +1 -5
- package/agents/plan-checker.md +1 -1
- package/agents/planner.md +2 -6
- package/agents/qa-browser.md +3 -3
- package/agents/roadmapper.md +2 -2
- package/agents/verifier.md +7 -9
- package/agents/visual-evaluator.md +1 -3
- package/bin/cli.js +370 -205
- package/bin/erp-retry.js +11 -3
- package/bin/install.js +383 -55
- package/bin/knowledge-flush.js +25 -13
- package/bin/knowledge.js +11 -1
- package/bin/project-snapshot.js +293 -0
- package/bin/qualia-ui.js +13 -2
- package/bin/report-payload.js +137 -0
- package/bin/slop-detect.mjs +81 -9
- package/bin/state.js +8 -1
- package/bin/statusline.js +14 -2
- package/docs/archive/CHANGELOG-pre-v4.md +855 -0
- package/docs/changelog-v6.html +864 -0
- package/docs/ecosystem-operating-model.md +121 -0
- package/docs/erp-contract.md +74 -21
- package/docs/onboarding.html +2 -2
- package/docs/release.md +44 -0
- package/docs/reviews/v6.2.1-revival-audit.md +53 -0
- package/docs/reviews/v6.2.2-memory-erp-audit.md +41 -0
- package/docs/reviews/v6.2.3-erp-id-guard.md +15 -0
- package/guide.md +28 -3
- package/hooks/auto-update.js +20 -10
- package/hooks/branch-guard.js +10 -2
- package/hooks/env-empty-guard.js +15 -5
- package/hooks/git-guardrails.js +10 -1
- package/hooks/migration-guard.js +4 -1
- package/hooks/pre-deploy-gate.js +11 -1
- package/hooks/pre-push.js +43 -106
- package/hooks/session-start.js +22 -14
- package/hooks/stop-session-log.js +11 -3
- package/hooks/supabase-destructive-guard.js +11 -1
- package/hooks/vercel-account-guard.js +12 -3
- package/package.json +4 -3
- package/qualia-design/design-reference.md +2 -1
- package/qualia-design/frontend.md +4 -4
- package/rules/one-opinion.md +59 -0
- package/rules/trust-boundary.md +35 -0
- package/skills/qualia-feature/SKILL.md +5 -5
- package/skills/qualia-flush/SKILL.md +5 -7
- package/skills/qualia-hook-gen/SKILL.md +1 -1
- package/skills/qualia-learn/SKILL.md +1 -0
- package/skills/qualia-map/SKILL.md +2 -1
- package/skills/qualia-milestone/SKILL.md +2 -2
- package/skills/qualia-new/SKILL.md +6 -6
- package/skills/qualia-optimize/SKILL.md +1 -1
- package/skills/qualia-plan/SKILL.md +1 -1
- package/skills/qualia-polish/REFERENCE.md +8 -6
- package/skills/qualia-polish/SKILL.md +11 -9
- package/skills/qualia-polish/scripts/loop.mjs +18 -6
- package/skills/qualia-postmortem/SKILL.md +1 -1
- package/skills/qualia-report/SKILL.md +6 -42
- package/skills/qualia-road/SKILL.md +17 -5
- package/skills/qualia-verify/SKILL.md +3 -3
- package/skills/qualia-vibe/SKILL.md +226 -0
- package/skills/qualia-vibe/scripts/extract.mjs +141 -0
- package/skills/qualia-vibe/scripts/tokens.mjs +342 -0
- package/templates/help.html +10 -3
- package/templates/knowledge/agents.md +3 -3
- package/templates/knowledge/index.md +1 -1
- package/templates/tracking.json +3 -0
- package/templates/work-packet.md +46 -0
- package/tests/bin.test.sh +423 -25
- package/tests/hooks.test.sh +1 -8
- package/tests/install-smoke.test.sh +137 -0
- package/tests/published-install-smoke.test.sh +126 -0
- package/tests/refs.test.sh +43 -1
- package/tests/run-all.sh +49 -0
- package/tests/runner.js +19 -33
- package/tests/slop-detect.test.sh +11 -5
- package/tests/state.test.sh +4 -1
- package/hooks/pre-compact.js +0 -125
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install-smoke.test.sh — package-level install smoke test.
|
|
3
|
+
# Builds the npm tarball, extracts it, and runs the packaged installer into an
|
|
4
|
+
# isolated HOME for the Claude + Codex target. This catches files that pass
|
|
5
|
+
# source-tree tests but are missing from the published package.
|
|
6
|
+
|
|
7
|
+
set -u
|
|
8
|
+
|
|
9
|
+
PASS=0
|
|
10
|
+
FAIL=0
|
|
11
|
+
|
|
12
|
+
pass() {
|
|
13
|
+
echo " ✓ $1"
|
|
14
|
+
PASS=$((PASS + 1))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fail_case() {
|
|
18
|
+
echo " ✗ $1"
|
|
19
|
+
[ -n "${2:-}" ] && echo " $2"
|
|
20
|
+
FAIL=$((FAIL + 1))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
24
|
+
NODE="${NODE:-node}"
|
|
25
|
+
TMP="$(mktemp -d 2>/dev/null || mktemp -d -t qualia-install-smoke)"
|
|
26
|
+
HOME_DIR="$TMP/home"
|
|
27
|
+
PACK_DIR="$TMP/pack"
|
|
28
|
+
CACHE_DIR="$TMP/npm-cache"
|
|
29
|
+
LOGS_DIR="$TMP/npm-logs"
|
|
30
|
+
USERCONFIG="$TMP/npmrc"
|
|
31
|
+
mkdir -p "$HOME_DIR" "$PACK_DIR" "$CACHE_DIR" "$LOGS_DIR"
|
|
32
|
+
: >"$USERCONFIG"
|
|
33
|
+
|
|
34
|
+
cleanup() {
|
|
35
|
+
rm -rf "$TMP"
|
|
36
|
+
}
|
|
37
|
+
trap cleanup EXIT
|
|
38
|
+
|
|
39
|
+
echo "install-smoke.test.sh — packaged Claude + Codex install"
|
|
40
|
+
echo ""
|
|
41
|
+
|
|
42
|
+
PACK_OUT="$TMP/npm-pack.out"
|
|
43
|
+
PACK_ERR="$TMP/npm-pack.err"
|
|
44
|
+
(cd "$ROOT" && env \
|
|
45
|
+
NPM_CONFIG_CACHE="$CACHE_DIR" \
|
|
46
|
+
NPM_CONFIG_LOGS_DIR="$LOGS_DIR" \
|
|
47
|
+
NPM_CONFIG_USERCONFIG="$USERCONFIG" \
|
|
48
|
+
npm pack --silent --pack-destination "$PACK_DIR" >"$PACK_OUT" 2>"$PACK_ERR")
|
|
49
|
+
PACK_EXIT=$?
|
|
50
|
+
TARBALL=$(tail -1 "$PACK_OUT" 2>/dev/null | tr -d '[:space:]')
|
|
51
|
+
TARBALL_PATH="$PACK_DIR/$TARBALL"
|
|
52
|
+
|
|
53
|
+
if [ -f "$TARBALL_PATH" ]; then
|
|
54
|
+
pass "npm pack produced $TARBALL"
|
|
55
|
+
else
|
|
56
|
+
fail_case "npm pack tarball missing" "exit=$PACK_EXIT expected=$TARBALL_PATH stderr=$(cat "$PACK_ERR" 2>/dev/null)"
|
|
57
|
+
echo ""
|
|
58
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
tar -xzf "$TARBALL_PATH" -C "$TMP" 2>"$TMP/tar.err"
|
|
63
|
+
if [ -f "$TMP/package/bin/install.js" ] \
|
|
64
|
+
&& [ -f "$TMP/package/bin/report-payload.js" ] \
|
|
65
|
+
&& [ -f "$TMP/package/bin/project-snapshot.js" ] \
|
|
66
|
+
&& [ -f "$TMP/package/AGENTS.md" ] \
|
|
67
|
+
&& [ -f "$TMP/package/CLAUDE.md" ]; then
|
|
68
|
+
pass "tarball contains installer + Claude/Codex instruction roots"
|
|
69
|
+
else
|
|
70
|
+
fail_case "tarball missing install surfaces" "$(cat "$TMP/tar.err" 2>/dev/null)"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
printf 'QS-FAWZI-01\n3\n' | HOME="$HOME_DIR" "$NODE" "$TMP/package/bin/install.js" >"$TMP/install.log" 2>&1
|
|
74
|
+
EXIT=$?
|
|
75
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
76
|
+
pass "packaged installer exits 0 for target=Both"
|
|
77
|
+
else
|
|
78
|
+
fail_case "packaged installer failed" "exit=$EXIT log=$(tail -20 "$TMP/install.log" 2>/dev/null)"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [ -f "$HOME_DIR/.claude/CLAUDE.md" ] \
|
|
82
|
+
&& grep -q "Role: OWNER" "$HOME_DIR/.claude/CLAUDE.md" \
|
|
83
|
+
&& ! grep -q "{{ROLE}}" "$HOME_DIR/.claude/CLAUDE.md"; then
|
|
84
|
+
pass "Claude CLAUDE.md installed with OWNER role"
|
|
85
|
+
else
|
|
86
|
+
fail_case "Claude CLAUDE.md role substitution failed"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [ -f "$HOME_DIR/.codex/AGENTS.md" ] \
|
|
90
|
+
&& [ -f "$HOME_DIR/.codex/hooks.json" ] \
|
|
91
|
+
&& [ -f "$HOME_DIR/.codex/config.toml" ] \
|
|
92
|
+
&& [ -f "$HOME_DIR/.codex/bin/statusline.js" ] \
|
|
93
|
+
&& [ -f "$HOME_DIR/.codex/bin/project-snapshot.js" ] \
|
|
94
|
+
&& [ -f "$HOME_DIR/.codex/agents/planner.toml" ] \
|
|
95
|
+
&& [ -f "$HOME_DIR/.codex/skills/qualia-new/SKILL.md" ] \
|
|
96
|
+
&& [ -f "$HOME_DIR/.codex/qualia-references/questioning.md" ] \
|
|
97
|
+
&& grep -q "Role: OWNER" "$HOME_DIR/.codex/AGENTS.md" \
|
|
98
|
+
&& ! grep -q "{{ROLE}}" "$HOME_DIR/.codex/AGENTS.md" \
|
|
99
|
+
&& grep -q "Qualia deploy gate" "$HOME_DIR/.codex/hooks.json" \
|
|
100
|
+
&& ! grep -R "\.claude/bin" "$HOME_DIR/.codex/skills" >/dev/null 2>&1; then
|
|
101
|
+
pass "Codex runtime installed with OWNER role"
|
|
102
|
+
else
|
|
103
|
+
fail_case "Codex runtime install failed"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ -d "$HOME_DIR/.claude/hooks" ] \
|
|
107
|
+
&& [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "11" ] \
|
|
108
|
+
&& [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
|
|
109
|
+
pass "packaged install has 11 hooks and no pre-compact"
|
|
110
|
+
else
|
|
111
|
+
fail_case "packaged hook set mismatch"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [ -f "$HOME_DIR/.claude/bin/report-payload.js" ] \
|
|
115
|
+
&& [ -f "$HOME_DIR/.claude/bin/project-snapshot.js" ] \
|
|
116
|
+
&& grep -q "report-payload.js" "$HOME_DIR/.claude/skills/qualia-report/SKILL.md"; then
|
|
117
|
+
pass "packaged install includes ERP report/snapshot helpers"
|
|
118
|
+
else
|
|
119
|
+
fail_case "packaged install missing ERP report/snapshot helpers"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
PKG_VERSION=$("$NODE" -e "console.log(require(process.argv[1]).version)" "$TMP/package/package.json")
|
|
123
|
+
CONFIG_VERSION=$("$NODE" -e "console.log(require(process.argv[1]).version)" "$HOME_DIR/.claude/.qualia-config.json" 2>/dev/null || echo "")
|
|
124
|
+
if [ "$PKG_VERSION" = "$CONFIG_VERSION" ]; then
|
|
125
|
+
pass "installed config version matches package ($PKG_VERSION)"
|
|
126
|
+
else
|
|
127
|
+
fail_case "installed config version mismatch" "package=$PKG_VERSION config=$CONFIG_VERSION"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
echo ""
|
|
131
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
132
|
+
|
|
133
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
exit 0
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# published-install-smoke.test.sh — release-only smoke for the public npm path.
|
|
3
|
+
# This intentionally is not part of npm test: it should fail until the current
|
|
4
|
+
# package.json version has been published to the npm latest dist-tag.
|
|
5
|
+
|
|
6
|
+
set -u
|
|
7
|
+
|
|
8
|
+
PASS=0
|
|
9
|
+
FAIL=0
|
|
10
|
+
|
|
11
|
+
pass() {
|
|
12
|
+
echo " ✓ $1"
|
|
13
|
+
PASS=$((PASS + 1))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fail_case() {
|
|
17
|
+
echo " ✗ $1"
|
|
18
|
+
[ -n "${2:-}" ] && echo " $2"
|
|
19
|
+
FAIL=$((FAIL + 1))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
23
|
+
NODE="${NODE:-node}"
|
|
24
|
+
TMP="$(mktemp -d 2>/dev/null || mktemp -d -t qualia-published-install-smoke)"
|
|
25
|
+
HOME_DIR="$TMP/home"
|
|
26
|
+
CACHE_DIR="$TMP/npm-cache"
|
|
27
|
+
LOGS_DIR="$TMP/npm-logs"
|
|
28
|
+
USERCONFIG="$TMP/npmrc"
|
|
29
|
+
mkdir -p "$HOME_DIR" "$CACHE_DIR" "$LOGS_DIR"
|
|
30
|
+
: >"$USERCONFIG"
|
|
31
|
+
REGISTRY_TIMEOUT_SECONDS="${REGISTRY_TIMEOUT_SECONDS:-45}"
|
|
32
|
+
INSTALL_TIMEOUT_SECONDS="${INSTALL_TIMEOUT_SECONDS:-120}"
|
|
33
|
+
|
|
34
|
+
cleanup() {
|
|
35
|
+
rm -rf "$TMP"
|
|
36
|
+
}
|
|
37
|
+
trap cleanup EXIT
|
|
38
|
+
|
|
39
|
+
echo "published-install-smoke.test.sh — npm @latest Claude + Codex install"
|
|
40
|
+
echo ""
|
|
41
|
+
|
|
42
|
+
LOCAL_VERSION=$("$NODE" -e "console.log(require(process.argv[1]).version)" "$ROOT/package.json")
|
|
43
|
+
timeout "$REGISTRY_TIMEOUT_SECONDS" env \
|
|
44
|
+
NPM_CONFIG_CACHE="$CACHE_DIR" \
|
|
45
|
+
NPM_CONFIG_LOGS_DIR="$LOGS_DIR" \
|
|
46
|
+
NPM_CONFIG_USERCONFIG="$USERCONFIG" \
|
|
47
|
+
npm view qualia-framework version >"$TMP/npm-view.out" 2>"$TMP/npm-view.err"
|
|
48
|
+
VIEW_EXIT=$?
|
|
49
|
+
LATEST_VERSION=$(tail -1 "$TMP/npm-view.out" 2>/dev/null | tr -d '[:space:]')
|
|
50
|
+
|
|
51
|
+
if [ "$VIEW_EXIT" -eq 124 ]; then
|
|
52
|
+
fail_case "npm view timed out" "after=${REGISTRY_TIMEOUT_SECONDS}s"
|
|
53
|
+
echo ""
|
|
54
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
55
|
+
exit 1
|
|
56
|
+
elif [ "$VIEW_EXIT" -ne 0 ]; then
|
|
57
|
+
fail_case "npm view failed" "exit=$VIEW_EXIT err=$(cat "$TMP/npm-view.err" 2>/dev/null)"
|
|
58
|
+
echo ""
|
|
59
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [ "$LATEST_VERSION" = "$LOCAL_VERSION" ]; then
|
|
64
|
+
pass "npm latest matches package.json ($LOCAL_VERSION)"
|
|
65
|
+
else
|
|
66
|
+
fail_case "npm latest does not match package.json" "package.json=$LOCAL_VERSION npm_latest=${LATEST_VERSION:-unavailable} npm_err=$(cat "$TMP/npm-view.err" 2>/dev/null)"
|
|
67
|
+
echo ""
|
|
68
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
printf 'QS-FAWZI-01\n3\n' >"$TMP/install.input"
|
|
73
|
+
timeout "$INSTALL_TIMEOUT_SECONDS" env \
|
|
74
|
+
HOME="$HOME_DIR" \
|
|
75
|
+
NPM_CONFIG_CACHE="$CACHE_DIR" \
|
|
76
|
+
NPM_CONFIG_LOGS_DIR="$LOGS_DIR" \
|
|
77
|
+
NPM_CONFIG_USERCONFIG="$USERCONFIG" \
|
|
78
|
+
npx --yes qualia-framework@latest install <"$TMP/install.input" >"$TMP/install.log" 2>&1
|
|
79
|
+
EXIT=$?
|
|
80
|
+
if [ "$EXIT" -eq 0 ]; then
|
|
81
|
+
pass "npx qualia-framework@latest install exits 0 for target=Both"
|
|
82
|
+
elif [ "$EXIT" -eq 124 ]; then
|
|
83
|
+
fail_case "npx qualia-framework@latest install timed out" "after=${INSTALL_TIMEOUT_SECONDS}s log=$(tail -30 "$TMP/install.log" 2>/dev/null)"
|
|
84
|
+
else
|
|
85
|
+
fail_case "npx qualia-framework@latest install failed" "exit=$EXIT log=$(tail -30 "$TMP/install.log" 2>/dev/null)"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ -f "$HOME_DIR/.claude/CLAUDE.md" ] \
|
|
89
|
+
&& grep -q "Role: OWNER" "$HOME_DIR/.claude/CLAUDE.md" \
|
|
90
|
+
&& ! grep -q "{{ROLE}}" "$HOME_DIR/.claude/CLAUDE.md"; then
|
|
91
|
+
pass "public install writes Claude CLAUDE.md with OWNER role"
|
|
92
|
+
else
|
|
93
|
+
fail_case "public install Claude CLAUDE.md role substitution failed"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
if [ -f "$HOME_DIR/.codex/AGENTS.md" ] \
|
|
97
|
+
&& grep -q "Role: OWNER" "$HOME_DIR/.codex/AGENTS.md" \
|
|
98
|
+
&& ! grep -q "{{ROLE}}" "$HOME_DIR/.codex/AGENTS.md"; then
|
|
99
|
+
pass "public install writes Codex AGENTS.md with OWNER role"
|
|
100
|
+
else
|
|
101
|
+
fail_case "public install Codex AGENTS.md role substitution failed"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if [ -d "$HOME_DIR/.claude/hooks" ] \
|
|
105
|
+
&& [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "11" ] \
|
|
106
|
+
&& [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
|
|
107
|
+
pass "public install has 11 hooks and no pre-compact"
|
|
108
|
+
else
|
|
109
|
+
fail_case "public install hook set mismatch"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
CONFIG_VERSION=$("$NODE" -e "console.log(require(process.argv[1]).version)" "$HOME_DIR/.claude/.qualia-config.json" 2>/dev/null || echo "")
|
|
113
|
+
if [ "$LOCAL_VERSION" = "$CONFIG_VERSION" ]; then
|
|
114
|
+
pass "public install config version matches package ($LOCAL_VERSION)"
|
|
115
|
+
else
|
|
116
|
+
fail_case "public install config version mismatch" "package=$LOCAL_VERSION config=$CONFIG_VERSION"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo ""
|
|
120
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
121
|
+
|
|
122
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
exit 0
|
package/tests/refs.test.sh
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
FRAMEWORK_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
23
23
|
SKILLS_DIR="$FRAMEWORK_ROOT/skills"
|
|
24
|
+
NODE="${NODE:-node}"
|
|
24
25
|
|
|
25
26
|
PASS=0
|
|
26
27
|
FAIL=0
|
|
@@ -42,7 +43,7 @@ EXCLUDE_REGEX='/docs/reviews/|/docs/research/|/docs/playwright-loop-pilot-result
|
|
|
42
43
|
# When a `/qualia-foo` ref appears AFTER one of these context tokens on the same line,
|
|
43
44
|
# it's a migration-explainer ("Replaces /qualia-quick" / "deprecated in v5.7"), not
|
|
44
45
|
# a live command reference. Treat it as exempt.
|
|
45
|
-
MIGRATION_CONTEXT_REGEX='Replaces|Removed|removed in|consolidated|deprecated|renamed|former|previously|was the|now the|now\s+`?/qualia|absorbed|superseded|legacy|migrated|after\s+`?/qualia.*-(quick|task|prd|design|polish-loop)'
|
|
46
|
+
MIGRATION_CONTEXT_REGEX='Replaces|Removed|removed in|consolidated|deprecated|renamed|former|previously|was the|now the|now\s+`?/qualia|absorbed|superseded|legacy|migrated|dead|after\s+`?/qualia.*-(quick|task|prd|design|polish-loop)'
|
|
46
47
|
|
|
47
48
|
ACTIVE_DIRS=(
|
|
48
49
|
"$FRAMEWORK_ROOT/rules"
|
|
@@ -130,6 +131,47 @@ for ref in $(printf '%s\n' "${!SEEN_REFS[@]}" | sort); do
|
|
|
130
131
|
fail_case "$ref" "no skills/$name/SKILL.md — referenced by: $locations"
|
|
131
132
|
done
|
|
132
133
|
|
|
134
|
+
PACKAGE_VERSION="$("$NODE" -e 'console.log(require(process.argv[1]).version)' "$FRAMEWORK_ROOT/package.json" 2>/dev/null || echo "")"
|
|
135
|
+
if [ -n "$PACKAGE_VERSION" ] && grep -q "# Qualia Framework v$PACKAGE_VERSION" "$FRAMEWORK_ROOT/README.md"; then
|
|
136
|
+
pass "README title matches package.json version ($PACKAGE_VERSION)"
|
|
137
|
+
else
|
|
138
|
+
fail_case "README version drift" "README.md title must match package.json version $PACKAGE_VERSION"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
forbidden_surface_patterns=(
|
|
142
|
+
'ERP reads.*tracking\.json'
|
|
143
|
+
'tracking\.json.*ERP reads'
|
|
144
|
+
'reads it via git'
|
|
145
|
+
'reads it automatically'
|
|
146
|
+
'passive monitoring'
|
|
147
|
+
'Pre-compact.*Saves state'
|
|
148
|
+
'Stamps tracking\.json via a bot commit'
|
|
149
|
+
'Surface stays at 32 skills'
|
|
150
|
+
'Same 32 skills'
|
|
151
|
+
'orchestrator treats.*silent PASS'
|
|
152
|
+
'skip silently and note'
|
|
153
|
+
'"client_id": "acme"'
|
|
154
|
+
'"workspace_id": "qualia-solutions"'
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
for pattern in "${forbidden_surface_patterns[@]}"; do
|
|
158
|
+
hits=""
|
|
159
|
+
while IFS= read -r file; do
|
|
160
|
+
[ -z "$file" ] && continue
|
|
161
|
+
found=$(grep -nE "$pattern" "$file" 2>/dev/null || true)
|
|
162
|
+
if [ -n "$found" ]; then
|
|
163
|
+
rel="${file#$FRAMEWORK_ROOT/}"
|
|
164
|
+
hits="${hits}${hits:+; }${rel}: $(echo "$found" | head -1)"
|
|
165
|
+
fi
|
|
166
|
+
done <<<"$SCAN_FILES"
|
|
167
|
+
|
|
168
|
+
if [ -n "$hits" ]; then
|
|
169
|
+
fail_case "forbidden stale surface pattern: $pattern" "$hits"
|
|
170
|
+
else
|
|
171
|
+
pass "no stale surface pattern: $pattern"
|
|
172
|
+
fi
|
|
173
|
+
done
|
|
174
|
+
|
|
133
175
|
echo ""
|
|
134
176
|
echo "Results: $PASS passed, $FAIL failed"
|
|
135
177
|
|
package/tests/run-all.sh
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tests/run-all.sh — fail-collect test orchestrator.
|
|
3
|
+
# Runs every suite even if earlier ones fail, then reports which suites failed.
|
|
4
|
+
# Exits non-zero if any suite failed.
|
|
5
|
+
|
|
6
|
+
set -u
|
|
7
|
+
|
|
8
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
|
|
10
|
+
SUITES=(
|
|
11
|
+
"statusline"
|
|
12
|
+
"state"
|
|
13
|
+
"hooks"
|
|
14
|
+
"bin"
|
|
15
|
+
"lib"
|
|
16
|
+
"skills"
|
|
17
|
+
"refs"
|
|
18
|
+
"install-smoke"
|
|
19
|
+
"slop-detect"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
FAILED=()
|
|
23
|
+
|
|
24
|
+
for suite in "${SUITES[@]}"; do
|
|
25
|
+
file="$DIR/$suite.test.sh"
|
|
26
|
+
if [ ! -f "$file" ]; then
|
|
27
|
+
echo "MISSING: $file"
|
|
28
|
+
FAILED+=("$suite (missing file)")
|
|
29
|
+
continue
|
|
30
|
+
fi
|
|
31
|
+
if bash "$file"; then
|
|
32
|
+
:
|
|
33
|
+
else
|
|
34
|
+
FAILED+=("$suite")
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
echo ""
|
|
39
|
+
echo "===================================================="
|
|
40
|
+
if [ ${#FAILED[@]} -eq 0 ]; then
|
|
41
|
+
echo "All ${#SUITES[@]} suites passed."
|
|
42
|
+
exit 0
|
|
43
|
+
else
|
|
44
|
+
echo "${#FAILED[@]} of ${#SUITES[@]} suites FAILED:"
|
|
45
|
+
for s in "${FAILED[@]}"; do
|
|
46
|
+
echo " - $s"
|
|
47
|
+
done
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
package/tests/runner.js
CHANGED
|
@@ -147,6 +147,7 @@ describe("CLI", () => {
|
|
|
147
147
|
assert.match(clean, /analytics/);
|
|
148
148
|
assert.match(clean, /set-erp-key/);
|
|
149
149
|
assert.match(clean, /erp-ping/);
|
|
150
|
+
assert.match(clean, /project-snapshot/);
|
|
150
151
|
assert.match(clean, /doctor/);
|
|
151
152
|
assert.match(clean, /flush/);
|
|
152
153
|
} finally {
|
|
@@ -987,6 +988,9 @@ waves: 1
|
|
|
987
988
|
// New v3.6 fields (default to empty / 0, but must be present)
|
|
988
989
|
assert.ok("team_id" in t, "team_id missing");
|
|
989
990
|
assert.ok("project_id" in t, "project_id missing");
|
|
991
|
+
assert.ok("erp_project_id" in t, "erp_project_id missing");
|
|
992
|
+
assert.ok("client_id" in t, "client_id missing");
|
|
993
|
+
assert.ok("workspace_id" in t, "workspace_id missing");
|
|
990
994
|
assert.ok("git_remote" in t, "git_remote missing");
|
|
991
995
|
assert.ok("session_started_at" in t, "session_started_at missing");
|
|
992
996
|
assert.ok("last_pushed_at" in t, "last_pushed_at missing");
|
|
@@ -1507,25 +1511,18 @@ describe("Hooks", () => {
|
|
|
1507
1511
|
|
|
1508
1512
|
// v3.4.2: behavioral test — the stamp must actually mutate tracking.json
|
|
1509
1513
|
// AND create a real commit so the push includes it.
|
|
1510
|
-
//
|
|
1511
|
-
//
|
|
1512
|
-
//
|
|
1513
|
-
|
|
1514
|
-
// commands (defensive), but the test's seed-commit path still hits an
|
|
1515
|
-
// edge case on Windows that needs platform-specific investigation. This
|
|
1516
|
-
// is tracked as a v4.1.2 follow-up; the Linux+macOS paths (which are the
|
|
1517
|
-
// overwhelming majority of installs) are fully covered here.
|
|
1518
|
-
it("pre-push.js mutates tracking.json AND commits the stamp", { skip: process.platform === "win32" ? "pre-existing autocrlf edge case — investigate in v4.1.2" : false }, () => {
|
|
1514
|
+
// v6.2.0: pre-push.js no longer commits — it stamps tracking.json locally
|
|
1515
|
+
// only. Test asserts the inverse of the pre-v6.2 behavior: file is mutated,
|
|
1516
|
+
// HEAD is unchanged, file shows as modified in the working tree.
|
|
1517
|
+
it("pre-push.js stamps tracking.json locally without creating a commit", () => {
|
|
1519
1518
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-push-real-"));
|
|
1520
1519
|
try {
|
|
1521
|
-
// Init a real git repo
|
|
1522
1520
|
const gitOpts = { cwd: tmpDir, encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] };
|
|
1523
1521
|
spawnSync("git", ["init", "--initial-branch=main"], gitOpts);
|
|
1524
1522
|
spawnSync("git", ["config", "user.email", "test@example.com"], gitOpts);
|
|
1525
1523
|
spawnSync("git", ["config", "user.name", "Test"], gitOpts);
|
|
1526
1524
|
spawnSync("git", ["config", "commit.gpgsign", "false"], gitOpts);
|
|
1527
1525
|
|
|
1528
|
-
// Seed .planning/tracking.json + an initial commit
|
|
1529
1526
|
fs.mkdirSync(path.join(tmpDir, ".planning"));
|
|
1530
1527
|
const tFile = path.join(tmpDir, ".planning", "tracking.json");
|
|
1531
1528
|
fs.writeFileSync(tFile, JSON.stringify({
|
|
@@ -1536,25 +1533,25 @@ describe("Hooks", () => {
|
|
|
1536
1533
|
|
|
1537
1534
|
const headBefore = spawnSync("git", ["rev-parse", "HEAD"], gitOpts).stdout.trim();
|
|
1538
1535
|
|
|
1539
|
-
// Run the hook
|
|
1540
1536
|
const r = spawnSync(NODE, [path.join(HOOKS, "pre-push.js")], {
|
|
1541
1537
|
encoding: "utf8", cwd: tmpDir, timeout: 10000, stdio: ["pipe", "pipe", "pipe"],
|
|
1542
1538
|
});
|
|
1543
1539
|
assert.equal(r.status, 0, `pre-push exited ${r.status}: ${r.stderr}`);
|
|
1544
1540
|
|
|
1545
|
-
// tracking.json must have been mutated
|
|
1541
|
+
// tracking.json must have been mutated on disk
|
|
1546
1542
|
const t = JSON.parse(fs.readFileSync(tFile, "utf8"));
|
|
1547
1543
|
assert.notEqual(t.last_commit, "OLD", "last_commit should have been updated");
|
|
1548
1544
|
assert.notEqual(t.last_updated, "2020-01-01T00:00:00Z", "last_updated should have been updated");
|
|
1549
1545
|
assert.match(t.last_updated, /^\d{4}-\d{2}-\d{2}T/);
|
|
1546
|
+
assert.match(t.last_pushed_at, /^\d{4}-\d{2}-\d{2}T/, "last_pushed_at must be set");
|
|
1550
1547
|
|
|
1551
|
-
//
|
|
1548
|
+
// HEAD must NOT advance — v6.2.0 contract: no bot commit
|
|
1552
1549
|
const headAfter = spawnSync("git", ["rev-parse", "HEAD"], gitOpts).stdout.trim();
|
|
1553
|
-
assert.
|
|
1550
|
+
assert.equal(headAfter, headBefore, "pre-push must NOT create a commit (v6.2.0)");
|
|
1554
1551
|
|
|
1555
|
-
// The
|
|
1556
|
-
const
|
|
1557
|
-
assert.match(
|
|
1552
|
+
// The file should appear as modified-not-staged in the working tree
|
|
1553
|
+
const status = spawnSync("git", ["status", "--porcelain", "--", ".planning/tracking.json"], gitOpts).stdout;
|
|
1554
|
+
assert.match(status, /^ M /, "tracking.json should be unstaged modified");
|
|
1558
1555
|
} finally {
|
|
1559
1556
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1560
1557
|
}
|
|
@@ -1601,19 +1598,7 @@ describe("Hooks", () => {
|
|
|
1601
1598
|
}
|
|
1602
1599
|
});
|
|
1603
1600
|
|
|
1604
|
-
//
|
|
1605
|
-
|
|
1606
|
-
it("pre-compact.js exits 0 with no STATE.md", () => {
|
|
1607
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-pc-"));
|
|
1608
|
-
try {
|
|
1609
|
-
const r = spawnSync(NODE, [path.join(HOOKS, "pre-compact.js")], {
|
|
1610
|
-
encoding: "utf8", cwd: tmpDir, timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
1611
|
-
});
|
|
1612
|
-
assert.equal(r.status, 0);
|
|
1613
|
-
} finally {
|
|
1614
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1615
|
-
}
|
|
1616
|
-
});
|
|
1601
|
+
// pre-compact.js removed in v6.2.0 — state.js journal provides crash safety.
|
|
1617
1602
|
|
|
1618
1603
|
// --- auto-update.js ---
|
|
1619
1604
|
|
|
@@ -2593,12 +2578,13 @@ describe("install.js", () => {
|
|
|
2593
2578
|
}
|
|
2594
2579
|
});
|
|
2595
2580
|
|
|
2596
|
-
it("
|
|
2581
|
+
it("11 hooks installed (v6.2.0: pre-compact removed; v5.0: vercel-account-guard, env-empty-guard, supabase-destructive-guard added)", () => {
|
|
2597
2582
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2598
2583
|
try {
|
|
2599
2584
|
runInstall("QS-FAWZI-01", tmpHome);
|
|
2600
2585
|
const hooks = fs.readdirSync(path.join(tmpHome, ".claude", "hooks")).filter(f => f.endsWith(".js"));
|
|
2601
|
-
assert.equal(hooks.length,
|
|
2586
|
+
assert.equal(hooks.length, 11, `expected 11 hooks, got ${hooks.length}: ${hooks.join(", ")}`);
|
|
2587
|
+
assert.ok(!hooks.includes("pre-compact.js"), "pre-compact.js must NOT be installed (removed in v6.2.0)");
|
|
2602
2588
|
} finally {
|
|
2603
2589
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
2604
2590
|
}
|
|
@@ -86,11 +86,13 @@ export default function Page() {
|
|
|
86
86
|
return <p>Welcome — to our amazing platform</p>;
|
|
87
87
|
}
|
|
88
88
|
EOF
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
EXIT_CODE=0
|
|
90
|
+
OUT=$($NODE "$SLOP_DETECT" "$TMP2/emdash.tsx" 2>&1) || EXIT_CODE=$?
|
|
91
|
+
# Em-dash is HIGH (non-blocking) so exit MUST be 0; finding MUST be in output.
|
|
92
|
+
if [ "$EXIT_CODE" = "0" ] && echo "$OUT" | grep -qiE "em.?dash|—"; then
|
|
91
93
|
pass "reports em-dash finding (HIGH severity, non-blocking)"
|
|
92
94
|
else
|
|
93
|
-
fail_case "em-dash detection" "
|
|
95
|
+
fail_case "em-dash detection" "exit=$EXIT_CODE, output: $(echo "$OUT" | head -c 120)"
|
|
94
96
|
fi
|
|
95
97
|
|
|
96
98
|
# ── Banned-font detection ─────────────────────────────────────────────
|
|
@@ -138,8 +140,12 @@ fi
|
|
|
138
140
|
# ── --json flag produces JSON output ─────────────────────────────────
|
|
139
141
|
TMP5=$(mktmp)
|
|
140
142
|
cp "$TMP3/font.css" "$TMP5/font.css"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
# --json may exit 1 (banned font is CRITICAL); we only assert output shape.
|
|
144
|
+
EXIT_CODE=0
|
|
145
|
+
JSON_OUT=$($NODE "$SLOP_DETECT" --json "$TMP5/font.css" 2>/dev/null) || EXIT_CODE=$?
|
|
146
|
+
if [ "$EXIT_CODE" != "0" ] && [ "$EXIT_CODE" != "1" ]; then
|
|
147
|
+
fail_case "--json output" "unexpected exit=$EXIT_CODE (only 0/1 acceptable for CRITICAL)"
|
|
148
|
+
elif echo "$JSON_OUT" | head -1 | grep -qE "^[\{\[]"; then
|
|
143
149
|
pass "--json flag produces JSON-shaped output"
|
|
144
150
|
else
|
|
145
151
|
fail_case "--json output" "first line is not JSON-shaped: '$(echo "$JSON_OUT" | head -c 80)'"
|
package/tests/state.test.sh
CHANGED
|
@@ -109,7 +109,10 @@ fi
|
|
|
109
109
|
if grep -q '"project": "TestProject"' "$TMP/.planning/tracking.json" \
|
|
110
110
|
&& grep -q '"total_phases": 2' "$TMP/.planning/tracking.json" \
|
|
111
111
|
&& grep -q '"phase": 1' "$TMP/.planning/tracking.json" \
|
|
112
|
-
&& grep -q '"status": "setup"' "$TMP/.planning/tracking.json"
|
|
112
|
+
&& grep -q '"status": "setup"' "$TMP/.planning/tracking.json" \
|
|
113
|
+
&& grep -q '"erp_project_id": ""' "$TMP/.planning/tracking.json" \
|
|
114
|
+
&& grep -q '"client_id": ""' "$TMP/.planning/tracking.json" \
|
|
115
|
+
&& grep -q '"workspace_id": ""' "$TMP/.planning/tracking.json"; then
|
|
113
116
|
pass "cmdInit tracking.json has correct fields"
|
|
114
117
|
else
|
|
115
118
|
fail_case "cmdInit tracking.json fields"
|
package/hooks/pre-compact.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// ~/.claude/hooks/pre-compact.js — commit STATE.md before context compaction.
|
|
3
|
-
// PreCompact hook. Silent on failure — context compaction must never be blocked.
|
|
4
|
-
// Cross-platform (Windows/macOS/Linux).
|
|
5
|
-
//
|
|
6
|
-
// BY DEFAULT this commit uses --no-verify + --no-gpg-sign. The auto-save is a
|
|
7
|
-
// framework bot commit, and pre-commit hooks that run full test suites would
|
|
8
|
-
// routinely fail (context compaction happens at any moment) and lose the
|
|
9
|
-
// STATE.md snapshot. But compliance-sensitive projects can opt into strict
|
|
10
|
-
// mode via ~/.claude/.qualia-config.json:
|
|
11
|
-
//
|
|
12
|
-
// {
|
|
13
|
-
// "pre_compact": {
|
|
14
|
-
// "respect_user_hooks": true,
|
|
15
|
-
// "respect_gpg_signing": true
|
|
16
|
-
// }
|
|
17
|
-
// }
|
|
18
|
-
//
|
|
19
|
-
// When either is true, the corresponding --no-* flag is dropped.
|
|
20
|
-
|
|
21
|
-
const fs = require("fs");
|
|
22
|
-
const path = require("path");
|
|
23
|
-
const os = require("os");
|
|
24
|
-
const { spawnSync } = require("child_process");
|
|
25
|
-
|
|
26
|
-
const _traceStart = Date.now();
|
|
27
|
-
|
|
28
|
-
const STATE_FILE = path.join(".planning", "STATE.md");
|
|
29
|
-
const TRACKING_FILE = path.join(".planning", "tracking.json");
|
|
30
|
-
const PLANNING_FILES = [STATE_FILE, TRACKING_FILE];
|
|
31
|
-
const CONFIG_FILE = path.join(os.homedir(), ".claude", ".qualia-config.json");
|
|
32
|
-
|
|
33
|
-
function readCompactConfig() {
|
|
34
|
-
try {
|
|
35
|
-
const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
36
|
-
return cfg.pre_compact || {};
|
|
37
|
-
} catch {
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function git(args, opts = {}) {
|
|
43
|
-
return spawnSync("git", args, {
|
|
44
|
-
encoding: "utf8",
|
|
45
|
-
timeout: 3000,
|
|
46
|
-
shell: process.platform === "win32",
|
|
47
|
-
...opts,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function planningHasPendingChanges() {
|
|
52
|
-
// v5.0: also stage tracking.json. The CLAUDE.md compaction contract says to
|
|
53
|
-
// preserve tracking.json across compactions, but pre-v5 only committed
|
|
54
|
-
// STATE.md — tracking.json mutations from state.js transitions were lost
|
|
55
|
-
// across compactions, leaving the ERP with stale milestone/phase/tasks data.
|
|
56
|
-
const diff = git(["diff", "--name-only", "--", ...PLANNING_FILES]);
|
|
57
|
-
const diffLines = (diff.stdout || "").split(/\r?\n/);
|
|
58
|
-
if (PLANNING_FILES.some((f) => diffLines.includes(f))) return true;
|
|
59
|
-
|
|
60
|
-
const untracked = git(["ls-files", "--others", "--exclude-standard", "--", ...PLANNING_FILES]);
|
|
61
|
-
const untrackedLines = (untracked.stdout || "").split(/\r?\n/);
|
|
62
|
-
return PLANNING_FILES.some((f) => untrackedLines.includes(f));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let _commitStatus = null;
|
|
66
|
-
let _commitReason = "no-state-file";
|
|
67
|
-
let _commitFlags = null;
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
if (fs.existsSync(STATE_FILE)) {
|
|
71
|
-
console.log("QUALIA: Saving state before compaction...");
|
|
72
|
-
_commitReason = "state-clean";
|
|
73
|
-
// Check if STATE.md or tracking.json has tracked or untracked changes.
|
|
74
|
-
if (planningHasPendingChanges()) {
|
|
75
|
-
// Stage both planning files. `git add` silently no-ops on files that don't exist
|
|
76
|
-
// or have no changes, so passing both is safe even when only one is dirty.
|
|
77
|
-
const addRes = git(["add", ...PLANNING_FILES]);
|
|
78
|
-
const cfg = readCompactConfig();
|
|
79
|
-
const commitArgs = ["commit"];
|
|
80
|
-
if (!cfg.respect_user_hooks) commitArgs.push("--no-verify");
|
|
81
|
-
if (!cfg.respect_gpg_signing) commitArgs.push("--no-gpg-sign");
|
|
82
|
-
commitArgs.push("--author=Qualia Framework <bot@qualia.solutions>");
|
|
83
|
-
commitArgs.push("-m", "state: pre-compaction save (STATE.md + tracking.json)");
|
|
84
|
-
_commitFlags = {
|
|
85
|
-
no_verify: !cfg.respect_user_hooks,
|
|
86
|
-
no_gpg_sign: !cfg.respect_gpg_signing,
|
|
87
|
-
};
|
|
88
|
-
const commitRes = spawnSync("git", commitArgs, {
|
|
89
|
-
timeout: 5000,
|
|
90
|
-
stdio: ["ignore", "ignore", "pipe"],
|
|
91
|
-
encoding: "utf8",
|
|
92
|
-
shell: process.platform === "win32",
|
|
93
|
-
});
|
|
94
|
-
_commitStatus = commitRes.status;
|
|
95
|
-
_commitReason = addRes.status === 0 && commitRes.status === 0
|
|
96
|
-
? "committed"
|
|
97
|
-
: "commit-failed";
|
|
98
|
-
} else {
|
|
99
|
-
_commitReason = "state-clean";
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
// Silent — never block compaction
|
|
104
|
-
_commitReason = "exception";
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function _trace(hookName, result, extra) {
|
|
108
|
-
try {
|
|
109
|
-
const os = require("os");
|
|
110
|
-
const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
|
|
111
|
-
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
112
|
-
const entry = {
|
|
113
|
-
hook: hookName,
|
|
114
|
-
result,
|
|
115
|
-
timestamp: new Date().toISOString(),
|
|
116
|
-
duration_ms: Date.now() - _traceStart,
|
|
117
|
-
...extra,
|
|
118
|
-
};
|
|
119
|
-
const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
120
|
-
fs.appendFileSync(file, JSON.stringify(entry) + "\n");
|
|
121
|
-
} catch {}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
_trace("pre-compact", "allow", { commit_status: _commitStatus, commit_reason: _commitReason, commit_flags: _commitFlags });
|
|
125
|
-
process.exit(0);
|