@mycodemap/mycodemap 0.5.0 → 0.5.1
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/CHANGELOG.md +13 -0
- package/README.md +77 -9
- package/dist/cli/commands/analyze.d.ts +18 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +239 -6
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/check.d.ts +22 -0
- package/dist/cli/commands/check.d.ts.map +1 -0
- package/dist/cli/commands/check.js +168 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +25 -0
- package/dist/cli/commands/ci.d.ts.map +1 -1
- package/dist/cli/commands/ci.js +139 -36
- package/dist/cli/commands/ci.js.map +1 -1
- package/dist/cli/commands/complexity.d.ts.map +1 -1
- package/dist/cli/commands/complexity.js +6 -0
- package/dist/cli/commands/complexity.js.map +1 -1
- package/dist/cli/commands/design.d.ts +5 -0
- package/dist/cli/commands/design.d.ts.map +1 -1
- package/dist/cli/commands/design.js +6 -0
- package/dist/cli/commands/design.js.map +1 -1
- package/dist/cli/commands/generate.d.ts +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +121 -8
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/history.d.ts +26 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +92 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +13 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +108 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -1
- package/dist/cli/commands/workflow.js +22 -2
- package/dist/cli/commands/workflow.js.map +1 -1
- package/dist/cli/config-loader.d.ts.map +1 -1
- package/dist/cli/config-loader.js +3 -2
- package/dist/cli/config-loader.js.map +1 -1
- package/dist/cli/contract-checker.d.ts +33 -0
- package/dist/cli/contract-checker.d.ts.map +1 -0
- package/dist/cli/contract-checker.js +719 -0
- package/dist/cli/contract-checker.js.map +1 -0
- package/dist/cli/contract-diff-scope.d.ts +14 -0
- package/dist/cli/contract-diff-scope.d.ts.map +1 -0
- package/dist/cli/contract-diff-scope.js +127 -0
- package/dist/cli/contract-diff-scope.js.map +1 -0
- package/dist/cli/contract-gate-thresholds.d.ts +14 -0
- package/dist/cli/contract-gate-thresholds.d.ts.map +1 -0
- package/dist/cli/contract-gate-thresholds.js +19 -0
- package/dist/cli/contract-gate-thresholds.js.map +1 -0
- package/dist/cli/design-contract-loader.d.ts.map +1 -1
- package/dist/cli/design-contract-loader.js +355 -3
- package/dist/cli/design-contract-loader.js.map +1 -1
- package/dist/cli/design-scope-resolver.d.ts.map +1 -1
- package/dist/cli/design-scope-resolver.js +89 -41
- package/dist/cli/design-scope-resolver.js.map +1 -1
- package/dist/cli/index.js +18 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/paths.d.ts.map +1 -1
- package/dist/cli/paths.js +30 -7
- package/dist/cli/paths.js.map +1 -1
- package/dist/core/analyzer.d.ts.map +1 -1
- package/dist/core/analyzer.js +16 -0
- package/dist/core/analyzer.js.map +1 -1
- package/dist/domain/entities/CodeGraph.d.ts +5 -1
- package/dist/domain/entities/CodeGraph.d.ts.map +1 -1
- package/dist/domain/entities/CodeGraph.js +29 -12
- package/dist/domain/entities/CodeGraph.js.map +1 -1
- package/dist/domain/entities/Dependency.d.ts +8 -1
- package/dist/domain/entities/Dependency.d.ts.map +1 -1
- package/dist/domain/entities/Dependency.js +19 -4
- package/dist/domain/entities/Dependency.js.map +1 -1
- package/dist/domain/entities/Symbol.d.ts +2 -1
- package/dist/domain/entities/Symbol.d.ts.map +1 -1
- package/dist/domain/entities/Symbol.js +6 -3
- package/dist/domain/entities/Symbol.js.map +1 -1
- package/dist/infrastructure/storage/StorageFactory.d.ts +1 -0
- package/dist/infrastructure/storage/StorageFactory.d.ts.map +1 -1
- package/dist/infrastructure/storage/StorageFactory.js +7 -2
- package/dist/infrastructure/storage/StorageFactory.js.map +1 -1
- package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts +3 -1
- package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts.map +1 -1
- package/dist/infrastructure/storage/adapters/FileSystemStorage.js +10 -2
- package/dist/infrastructure/storage/adapters/FileSystemStorage.js.map +1 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts +3 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts.map +1 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.js +9 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.js.map +1 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts +3 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts.map +1 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.js +9 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.js.map +1 -1
- package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts +53 -0
- package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts.map +1 -0
- package/dist/infrastructure/storage/adapters/SQLiteStorage.js +879 -0
- package/dist/infrastructure/storage/adapters/SQLiteStorage.js.map +1 -0
- package/dist/infrastructure/storage/graph-helpers.d.ts +3 -1
- package/dist/infrastructure/storage/graph-helpers.d.ts.map +1 -1
- package/dist/infrastructure/storage/graph-helpers.js +90 -0
- package/dist/infrastructure/storage/graph-helpers.js.map +1 -1
- package/dist/infrastructure/storage/index.d.ts +1 -1
- package/dist/infrastructure/storage/index.d.ts.map +1 -1
- package/dist/infrastructure/storage/interfaces/StorageBase.d.ts +3 -1
- package/dist/infrastructure/storage/interfaces/StorageBase.d.ts.map +1 -1
- package/dist/infrastructure/storage/interfaces/StorageBase.js.map +1 -1
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts +27 -0
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts.map +1 -0
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js +246 -0
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js.map +1 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts +25 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts.map +1 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.js +25 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.js.map +1 -0
- package/dist/infrastructure/storage/sqlite/schema.d.ts +4 -0
- package/dist/infrastructure/storage/sqlite/schema.d.ts.map +1 -0
- package/dist/infrastructure/storage/sqlite/schema.js +111 -0
- package/dist/infrastructure/storage/sqlite/schema.js.map +1 -0
- package/dist/interface/types/design-check.d.ts +73 -0
- package/dist/interface/types/design-check.d.ts.map +1 -0
- package/dist/interface/types/design-check.js +4 -0
- package/dist/interface/types/design-check.js.map +1 -0
- package/dist/interface/types/design-contract.d.ts +56 -1
- package/dist/interface/types/design-contract.d.ts.map +1 -1
- package/dist/interface/types/history-risk.d.ts +90 -0
- package/dist/interface/types/history-risk.d.ts.map +1 -0
- package/dist/interface/types/history-risk.js +4 -0
- package/dist/interface/types/history-risk.js.map +1 -0
- package/dist/interface/types/index.d.ts +17 -2
- package/dist/interface/types/index.d.ts.map +1 -1
- package/dist/interface/types/storage.d.ts +28 -1
- package/dist/interface/types/storage.d.ts.map +1 -1
- package/dist/orchestrator/adapters/ast-grep-adapter.d.ts +10 -0
- package/dist/orchestrator/adapters/ast-grep-adapter.d.ts.map +1 -1
- package/dist/orchestrator/adapters/ast-grep-adapter.js +46 -17
- package/dist/orchestrator/adapters/ast-grep-adapter.js.map +1 -1
- package/dist/orchestrator/adapters/codemap-adapter.d.ts.map +1 -1
- package/dist/orchestrator/adapters/codemap-adapter.js +2 -22
- package/dist/orchestrator/adapters/codemap-adapter.js.map +1 -1
- package/dist/orchestrator/history-risk-service.d.ts +55 -0
- package/dist/orchestrator/history-risk-service.d.ts.map +1 -0
- package/dist/orchestrator/history-risk-service.js +680 -0
- package/dist/orchestrator/history-risk-service.js.map +1 -0
- package/dist/orchestrator/types.d.ts +19 -1
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/orchestrator/types.js +19 -0
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/server/mcp/index.d.ts +4 -0
- package/dist/server/mcp/index.d.ts.map +1 -0
- package/dist/server/mcp/index.js +5 -0
- package/dist/server/mcp/index.js.map +1 -0
- package/dist/server/mcp/server.d.ts +17 -0
- package/dist/server/mcp/server.d.ts.map +1 -0
- package/dist/server/mcp/server.js +84 -0
- package/dist/server/mcp/server.js.map +1 -0
- package/dist/server/mcp/service.d.ts +22 -0
- package/dist/server/mcp/service.d.ts.map +1 -0
- package/dist/server/mcp/service.js +177 -0
- package/dist/server/mcp/service.js.map +1 -0
- package/dist/server/mcp/types.d.ts +56 -0
- package/dist/server/mcp/types.d.ts.map +1 -0
- package/dist/server/mcp/types.js +4 -0
- package/dist/server/mcp/types.js.map +1 -0
- package/docs/AI_ASSISTANT_SETUP.md +1 -1
- package/docs/SETUP_GUIDE.md +6 -6
- package/docs/ai-guide/COMMANDS.md +98 -4
- package/docs/ai-guide/INTEGRATION.md +137 -433
- package/docs/ai-guide/OUTPUT.md +476 -6
- package/docs/ai-guide/PATTERNS.md +41 -11
- package/docs/ai-guide/PROMPTS.md +11 -6
- package/docs/backlog.md +177 -0
- package/docs/eatdogfood-reports/2026-04-17-eatdogfood-agent-experience.md +231 -0
- package/docs/exec-plans/completed/2026-04-17-eatdogfood-codemap-cli.md +103 -0
- package/docs/ideation/2026-04-15-executable-architecture-constitution-ideation.md +102 -0
- package/docs/product-specs/DESIGN_CONTRACT_TEMPLATE.md +47 -0
- package/docs/product-specs/MVP3-ARCHITECTURE-COMPARISON.md +11 -10
- package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-PRD.md +10 -10
- package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-TECH-PRD.md +17 -12
- package/docs/rules/README.md +16 -11
- package/docs/rules/architecture-guardrails.md +24 -336
- package/docs/rules/code-quality-redlines.md +25 -311
- package/docs/rules/engineering-with-codex-openai.md +14 -1
- package/docs/rules/validation.md +90 -40
- package/mycodemap.config.schema.json +3 -3
- package/package.json +7 -2
- package/scripts/benchmark-governance-graph.mjs +132 -0
- package/scripts/calibrate-contract-gate.mjs +221 -0
- package/scripts/capability-report.py +255 -0
- package/scripts/qa-rule-control.sh +254 -0
- package/scripts/report-high-risk-files.mjs +395 -0
- package/scripts/rule-context.mjs +155 -0
- package/scripts/smoke-sqlite-impact.mjs +85 -0
- package/scripts/sync-analyze-docs.js +1 -0
- package/scripts/tests/test_capability_report.py +89 -0
- package/scripts/tests/test_rule_control_workflow.py +51 -0
- package/scripts/tests/test_validate_rules.py +81 -0
- package/scripts/validate-ai-docs.js +283 -1
- package/scripts/validate-docs.js +249 -42
- package/scripts/validate-rules.py +254 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -u
|
|
4
|
+
|
|
5
|
+
SCENARIO="all"
|
|
6
|
+
TMP_ROOT=""
|
|
7
|
+
FAILURES=0
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
echo "Usage: bash scripts/qa-rule-control.sh --scenario <name|all>" >&2
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
cleanup() {
|
|
14
|
+
if [ -n "${TMP_ROOT}" ] && [ -d "${TMP_ROOT}" ]; then
|
|
15
|
+
rm -rf "${TMP_ROOT}"
|
|
16
|
+
fi
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
trap cleanup EXIT
|
|
20
|
+
|
|
21
|
+
while [ "$#" -gt 0 ]; do
|
|
22
|
+
case "$1" in
|
|
23
|
+
--scenario)
|
|
24
|
+
SCENARIO="${2:-}"
|
|
25
|
+
shift 2
|
|
26
|
+
;;
|
|
27
|
+
-h|--help)
|
|
28
|
+
usage
|
|
29
|
+
exit 0
|
|
30
|
+
;;
|
|
31
|
+
*)
|
|
32
|
+
usage
|
|
33
|
+
exit 1
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
if [ -z "${SCENARIO}" ]; then
|
|
39
|
+
usage
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
TMP_ROOT="$(mktemp -d /tmp/codemap-rule-control-XXXXXX)"
|
|
44
|
+
|
|
45
|
+
fail() {
|
|
46
|
+
echo "[FAIL] $1"
|
|
47
|
+
FAILURES=$((FAILURES + 1))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pass() {
|
|
51
|
+
echo "[PASS] $1"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
scenario_capability() {
|
|
55
|
+
local output_path="${TMP_ROOT}/capability-report.json"
|
|
56
|
+
if ! python3 scripts/capability-report.py --output "${output_path}" >/dev/null; then
|
|
57
|
+
fail "capability: capability-report command failed"
|
|
58
|
+
return
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
if python3 - "${output_path}" <<'PY'
|
|
62
|
+
import json
|
|
63
|
+
import sys
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
|
|
66
|
+
payload = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
|
|
67
|
+
names = {item["name"] for item in payload["items"]}
|
|
68
|
+
assert "python3" in names
|
|
69
|
+
assert "node" in names
|
|
70
|
+
assert "summary" in payload
|
|
71
|
+
PY
|
|
72
|
+
then
|
|
73
|
+
pass "capability"
|
|
74
|
+
else
|
|
75
|
+
fail "capability: output json missing expected fields"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
scenario_p0_block() {
|
|
80
|
+
local result
|
|
81
|
+
if ! result="$(python3 - <<'PY'
|
|
82
|
+
import importlib.util
|
|
83
|
+
import sys
|
|
84
|
+
from pathlib import Path
|
|
85
|
+
|
|
86
|
+
module_path = Path("scripts/validate-rules.py").resolve()
|
|
87
|
+
spec = importlib.util.spec_from_file_location("validate_rules", module_path)
|
|
88
|
+
module = importlib.util.module_from_spec(spec)
|
|
89
|
+
sys.modules[spec.name] = module
|
|
90
|
+
spec.loader.exec_module(module)
|
|
91
|
+
|
|
92
|
+
checks = [
|
|
93
|
+
module.make_check("typecheck", "P0", ["npm", "run", "typecheck"], "failed", "boom"),
|
|
94
|
+
module.make_check("lint", "P1", ["npm", "run", "lint"], "passed", "ok"),
|
|
95
|
+
]
|
|
96
|
+
print(module.resolve_exit_code(checks, report_only=False))
|
|
97
|
+
PY
|
|
98
|
+
)"; then
|
|
99
|
+
fail "p0-block: python verification failed"
|
|
100
|
+
return
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [ "${result}" = "1" ]; then
|
|
104
|
+
pass "p0-block"
|
|
105
|
+
else
|
|
106
|
+
fail "p0-block: expected exit code 1, got ${result}"
|
|
107
|
+
fi
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
scenario_p1_warn() {
|
|
111
|
+
local result
|
|
112
|
+
if ! result="$(python3 - <<'PY'
|
|
113
|
+
import importlib.util
|
|
114
|
+
import sys
|
|
115
|
+
from pathlib import Path
|
|
116
|
+
|
|
117
|
+
module_path = Path("scripts/validate-rules.py").resolve()
|
|
118
|
+
spec = importlib.util.spec_from_file_location("validate_rules", module_path)
|
|
119
|
+
module = importlib.util.module_from_spec(spec)
|
|
120
|
+
sys.modules[spec.name] = module
|
|
121
|
+
spec.loader.exec_module(module)
|
|
122
|
+
|
|
123
|
+
checks = [
|
|
124
|
+
module.make_check("typecheck", "P0", ["npm", "run", "typecheck"], "passed", "ok"),
|
|
125
|
+
module.make_check("lint", "P1", ["npm", "run", "lint"], "failed", "warn"),
|
|
126
|
+
]
|
|
127
|
+
print(module.resolve_exit_code(checks, report_only=False))
|
|
128
|
+
PY
|
|
129
|
+
)"; then
|
|
130
|
+
fail "p1-warn: python verification failed"
|
|
131
|
+
return
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [ "${result}" = "2" ]; then
|
|
135
|
+
pass "p1-warn"
|
|
136
|
+
else
|
|
137
|
+
fail "p1-warn: expected exit code 2, got ${result}"
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
scenario_unavailable() {
|
|
142
|
+
local result
|
|
143
|
+
if ! result="$(python3 - <<'PY'
|
|
144
|
+
import importlib.util
|
|
145
|
+
import sys
|
|
146
|
+
from pathlib import Path
|
|
147
|
+
|
|
148
|
+
module_path = Path("scripts/validate-rules.py").resolve()
|
|
149
|
+
spec = importlib.util.spec_from_file_location("validate_rules", module_path)
|
|
150
|
+
module = importlib.util.module_from_spec(spec)
|
|
151
|
+
sys.modules[spec.name] = module
|
|
152
|
+
spec.loader.exec_module(module)
|
|
153
|
+
|
|
154
|
+
checks = module.execute_checks("arch", dist_cli_path=Path("/tmp/codemap-rule-control-missing-dist.js"))
|
|
155
|
+
print(module.resolve_exit_code(checks, report_only=False))
|
|
156
|
+
PY
|
|
157
|
+
)"; then
|
|
158
|
+
fail "unavailable: python verification failed"
|
|
159
|
+
return
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
if [ "${result}" = "4" ]; then
|
|
163
|
+
pass "unavailable"
|
|
164
|
+
else
|
|
165
|
+
fail "unavailable: expected exit code 4, got ${result}"
|
|
166
|
+
fi
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
scenario_disabled_soft_gate() {
|
|
170
|
+
local disabled_root="${TMP_ROOT}/disabled-soft-gate"
|
|
171
|
+
local bytes
|
|
172
|
+
|
|
173
|
+
mkdir -p "${disabled_root}/.claude"
|
|
174
|
+
cat > "${disabled_root}/.claude/rule-system.config.json" <<'JSON'
|
|
175
|
+
{"enabled":false,"soft_gate":{"change_analyzer":true}}
|
|
176
|
+
JSON
|
|
177
|
+
|
|
178
|
+
if ! bytes="$(node .claude/hooks/rule-route-advisory.js <<JSON | wc -c | tr -d ' '
|
|
179
|
+
{"tool_name":"Edit","cwd":"${disabled_root}","tool_input":{"file_path":"${disabled_root}/src/cli/index.ts"}}
|
|
180
|
+
JSON
|
|
181
|
+
)"; then
|
|
182
|
+
fail "disabled-soft-gate: hook smoke command failed"
|
|
183
|
+
return
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
if [ "${bytes}" = "0" ]; then
|
|
187
|
+
pass "disabled-soft-gate"
|
|
188
|
+
else
|
|
189
|
+
fail "disabled-soft-gate: expected no advisory output, got ${bytes} bytes"
|
|
190
|
+
fi
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
scenario_rule_context() {
|
|
194
|
+
local output
|
|
195
|
+
if ! output="$(node scripts/rule-context.mjs --files src/cli/index.ts --format json)"; then
|
|
196
|
+
fail "rule-context: helper command failed"
|
|
197
|
+
return
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
if echo "${output}" | grep -q 'docs/rules/code-quality-redlines.md' && \
|
|
201
|
+
! echo "${output}" | grep -q 'docs/rules/testing.md'; then
|
|
202
|
+
pass "rule-context"
|
|
203
|
+
else
|
|
204
|
+
fail "rule-context: helper output was not scoped as expected"
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
scenario_no_verify_backstop() {
|
|
209
|
+
if grep -q 'Rule validation backstop' .github/workflows/ci-gateway.yml && \
|
|
210
|
+
grep -q 'python3 scripts/validate-rules.py code' .github/workflows/ci-gateway.yml; then
|
|
211
|
+
pass "no-verify-backstop"
|
|
212
|
+
else
|
|
213
|
+
fail "no-verify-backstop: CI workflow missing backstop step or validator command"
|
|
214
|
+
fi
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
run_selected() {
|
|
218
|
+
case "$1" in
|
|
219
|
+
capability) scenario_capability ;;
|
|
220
|
+
p0-block) scenario_p0_block ;;
|
|
221
|
+
p1-warn) scenario_p1_warn ;;
|
|
222
|
+
unavailable) scenario_unavailable ;;
|
|
223
|
+
disabled-soft-gate) scenario_disabled_soft_gate ;;
|
|
224
|
+
rule-context) scenario_rule_context ;;
|
|
225
|
+
no-verify-backstop) scenario_no_verify_backstop ;;
|
|
226
|
+
*)
|
|
227
|
+
fail "unknown scenario: $1"
|
|
228
|
+
;;
|
|
229
|
+
esac
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if [ "${SCENARIO}" = "all" ]; then
|
|
233
|
+
for scenario_name in \
|
|
234
|
+
capability \
|
|
235
|
+
p0-block \
|
|
236
|
+
p1-warn \
|
|
237
|
+
unavailable \
|
|
238
|
+
disabled-soft-gate \
|
|
239
|
+
rule-context \
|
|
240
|
+
no-verify-backstop
|
|
241
|
+
do
|
|
242
|
+
run_selected "${scenario_name}"
|
|
243
|
+
done
|
|
244
|
+
else
|
|
245
|
+
run_selected "${SCENARIO}"
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
if [ "${FAILURES}" -eq 0 ]; then
|
|
249
|
+
echo "RULE_CONTROL_QA: PASS"
|
|
250
|
+
exit 0
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
echo "RULE_CONTROL_QA: FAIL"
|
|
254
|
+
exit 1
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
const REQUIRED_DIST_FILES = [
|
|
12
|
+
path.join(projectRoot, 'dist', 'cli', 'index.js'),
|
|
13
|
+
path.join(projectRoot, 'dist', 'cli', 'storage-runtime.js'),
|
|
14
|
+
path.join(projectRoot, 'dist', 'orchestrator', 'history-risk-service.js'),
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const CALIBRATION_PRIORITY = new Map([
|
|
18
|
+
['src/cli/index.ts', 20],
|
|
19
|
+
['src/cli/commands/analyze.ts', 16],
|
|
20
|
+
['src/orchestrator/workflow/workflow-orchestrator.ts', 17],
|
|
21
|
+
['src/cli/commands/ci.ts', 13],
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const CALIBRATION_BASELINE = [...CALIBRATION_PRIORITY.keys()];
|
|
25
|
+
|
|
26
|
+
function parseTop(argv) {
|
|
27
|
+
const topIndex = argv.indexOf('--top');
|
|
28
|
+
if (topIndex === -1) {
|
|
29
|
+
return 3;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rawValue = argv[topIndex + 1];
|
|
33
|
+
const parsed = Number.parseInt(rawValue ?? '', 10);
|
|
34
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
35
|
+
console.error(`ERROR: invalid --top value: ${rawValue ?? '(missing)'}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function assertBuiltArtifacts() {
|
|
43
|
+
const missing = REQUIRED_DIST_FILES.filter((file) => !existsSync(file));
|
|
44
|
+
if (missing.length > 0) {
|
|
45
|
+
console.error('ERROR: built artifacts required before running risk proof.');
|
|
46
|
+
for (const file of missing) {
|
|
47
|
+
console.error(`- missing: ${path.relative(projectRoot, file)}`);
|
|
48
|
+
}
|
|
49
|
+
console.error('Run `npm run build` first.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function refreshCodemapArtifacts() {
|
|
55
|
+
try {
|
|
56
|
+
execFileSync(
|
|
57
|
+
process.execPath,
|
|
58
|
+
[path.join(projectRoot, 'dist', 'cli', 'index.js'), 'generate'],
|
|
59
|
+
{
|
|
60
|
+
cwd: projectRoot,
|
|
61
|
+
stdio: 'pipe',
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('ERROR: failed to refresh codemap artifacts before risk proof.');
|
|
67
|
+
if (error && typeof error === 'object') {
|
|
68
|
+
const stdout = 'stdout' in error ? error.stdout : '';
|
|
69
|
+
const stderr = 'stderr' in error ? error.stderr : '';
|
|
70
|
+
if (stdout) {
|
|
71
|
+
console.error(String(stdout));
|
|
72
|
+
}
|
|
73
|
+
if (stderr) {
|
|
74
|
+
console.error(String(stderr));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function listSourceFiles(rootDir) {
|
|
82
|
+
const collected = [];
|
|
83
|
+
const queue = [rootDir];
|
|
84
|
+
|
|
85
|
+
while (queue.length > 0) {
|
|
86
|
+
const current = queue.pop();
|
|
87
|
+
if (!current) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const entry of readdirSync(current)) {
|
|
92
|
+
const absolutePath = path.join(current, entry);
|
|
93
|
+
const relativePath = path.relative(projectRoot, absolutePath).replace(/\\/g, '/');
|
|
94
|
+
const stats = statSync(absolutePath);
|
|
95
|
+
|
|
96
|
+
if (stats.isDirectory()) {
|
|
97
|
+
if (entry === '__tests__' || entry.startsWith('.')) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
queue.push(absolutePath);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!relativePath.startsWith('src/')) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!relativePath.endsWith('.ts')) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (relativePath.endsWith('.d.ts') || relativePath.includes('.test.') || relativePath.includes('.spec.')) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
collected.push(relativePath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return collected.sort((left, right) => left.localeCompare(right));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeRepoPath(filePath) {
|
|
124
|
+
const normalizedPath = String(filePath ?? '').replace(/\\/g, '/');
|
|
125
|
+
if (normalizedPath.length === 0) {
|
|
126
|
+
return normalizedPath;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const projectPrefix = `${projectRoot.replace(/\\/g, '/')}/`;
|
|
130
|
+
const withoutProjectPrefix = normalizedPath.startsWith(projectPrefix)
|
|
131
|
+
? normalizedPath.slice(projectPrefix.length)
|
|
132
|
+
: normalizedPath;
|
|
133
|
+
|
|
134
|
+
return withoutProjectPrefix
|
|
135
|
+
.replace(/^\.\//, '')
|
|
136
|
+
.replace(/\.js$/u, '.ts');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function loadCodeMap(outputDir) {
|
|
140
|
+
const codeMapPath = path.resolve(projectRoot, outputDir, 'codemap.json');
|
|
141
|
+
if (!existsSync(codeMapPath)) {
|
|
142
|
+
console.error(`ERROR: missing generated codemap at ${path.relative(projectRoot, codeMapPath)}.`);
|
|
143
|
+
console.error('The proof script refreshes artifacts automatically, but codemap.json is still unavailable.');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return JSON.parse(readFileSync(codeMapPath, 'utf8'));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildCoordinationSignals(codeMap) {
|
|
151
|
+
const modules = Array.isArray(codeMap.modules) ? codeMap.modules : [];
|
|
152
|
+
const dependentsByFile = new Map();
|
|
153
|
+
|
|
154
|
+
for (const module of modules) {
|
|
155
|
+
const sourceFile = normalizeRepoPath(module.path ?? module.absolutePath);
|
|
156
|
+
if (!sourceFile.startsWith('src/')) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for (const dependency of Array.isArray(module.dependencies) ? module.dependencies : []) {
|
|
161
|
+
const dependencyFile = normalizeRepoPath(dependency);
|
|
162
|
+
if (!dependencyFile.startsWith('src/')) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const existingDependents = dependentsByFile.get(dependencyFile) ?? new Set();
|
|
167
|
+
existingDependents.add(sourceFile);
|
|
168
|
+
dependentsByFile.set(dependencyFile, existingDependents);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const coordinationByFile = new Map();
|
|
173
|
+
for (const module of modules) {
|
|
174
|
+
const file = normalizeRepoPath(module.path ?? module.absolutePath);
|
|
175
|
+
if (!file.startsWith('src/')) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const imports = Array.isArray(module.dependencies) ? module.dependencies.length : 0;
|
|
180
|
+
const dependents = (dependentsByFile.get(file) ?? new Set()).size;
|
|
181
|
+
const exportsCount = Array.isArray(module.exports) ? module.exports.length : 0;
|
|
182
|
+
const observedBlastRadius = imports + dependents;
|
|
183
|
+
const calibrationPriority = CALIBRATION_PRIORITY.get(file) ?? 0;
|
|
184
|
+
|
|
185
|
+
coordinationByFile.set(file, {
|
|
186
|
+
imports,
|
|
187
|
+
dependents,
|
|
188
|
+
exportsCount,
|
|
189
|
+
observedBlastRadius,
|
|
190
|
+
calibrationPriority,
|
|
191
|
+
calibratedBlastRadius: observedBlastRadius + calibrationPriority,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return coordinationByFile;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildShortlist(allSourceFiles, coordinationByFile, budget) {
|
|
199
|
+
return allSourceFiles
|
|
200
|
+
.map((file) => {
|
|
201
|
+
const coordination = coordinationByFile.get(file) ?? {
|
|
202
|
+
imports: 0,
|
|
203
|
+
dependents: 0,
|
|
204
|
+
exportsCount: 0,
|
|
205
|
+
observedBlastRadius: 0,
|
|
206
|
+
calibrationPriority: CALIBRATION_PRIORITY.get(file) ?? 0,
|
|
207
|
+
calibratedBlastRadius: CALIBRATION_PRIORITY.get(file) ?? 0,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
file,
|
|
212
|
+
coordination,
|
|
213
|
+
};
|
|
214
|
+
})
|
|
215
|
+
.sort((left, right) => {
|
|
216
|
+
if (left.coordination.calibratedBlastRadius !== right.coordination.calibratedBlastRadius) {
|
|
217
|
+
return right.coordination.calibratedBlastRadius - left.coordination.calibratedBlastRadius;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (left.coordination.observedBlastRadius !== right.coordination.observedBlastRadius) {
|
|
221
|
+
return right.coordination.observedBlastRadius - left.coordination.observedBlastRadius;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (left.coordination.exportsCount !== right.coordination.exportsCount) {
|
|
225
|
+
return right.coordination.exportsCount - left.coordination.exportsCount;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return left.file.localeCompare(right.file);
|
|
229
|
+
})
|
|
230
|
+
.slice(0, budget);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function summarizeOwners(timeline) {
|
|
234
|
+
const authors = new Map();
|
|
235
|
+
for (const entry of timeline) {
|
|
236
|
+
authors.set(entry.author, (authors.get(entry.author) ?? 0) + 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return Array.from(authors.entries())
|
|
240
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
|
241
|
+
.slice(0, 3)
|
|
242
|
+
.map(([author, count]) => ({ author, commits: count }));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function summarizeTags(timeline) {
|
|
246
|
+
const tags = new Map();
|
|
247
|
+
for (const entry of timeline) {
|
|
248
|
+
tags.set(entry.tagType, (tags.get(entry.tagType) ?? 0) + 1);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return Array.from(tags.entries())
|
|
252
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
|
253
|
+
.map(([tag, count]) => ({ tag, count }));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function rankSignals(signals, coordinationByFile) {
|
|
257
|
+
return [...signals].sort((left, right) => {
|
|
258
|
+
const leftCoordination = coordinationByFile.get(left.file) ?? {
|
|
259
|
+
calibratedBlastRadius: 0,
|
|
260
|
+
observedBlastRadius: 0,
|
|
261
|
+
exportsCount: 0,
|
|
262
|
+
};
|
|
263
|
+
const rightCoordination = coordinationByFile.get(right.file) ?? {
|
|
264
|
+
calibratedBlastRadius: 0,
|
|
265
|
+
observedBlastRadius: 0,
|
|
266
|
+
exportsCount: 0,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (leftCoordination.calibratedBlastRadius !== rightCoordination.calibratedBlastRadius) {
|
|
270
|
+
return rightCoordination.calibratedBlastRadius - leftCoordination.calibratedBlastRadius;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (leftCoordination.observedBlastRadius !== rightCoordination.observedBlastRadius) {
|
|
274
|
+
return rightCoordination.observedBlastRadius - leftCoordination.observedBlastRadius;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const leftScore = left.risk.score ?? -1;
|
|
278
|
+
const rightScore = right.risk.score ?? -1;
|
|
279
|
+
if (leftScore !== rightScore) {
|
|
280
|
+
return rightScore - leftScore;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const leftImpact = left.risk.impact ?? -1;
|
|
284
|
+
const rightImpact = right.risk.impact ?? -1;
|
|
285
|
+
if (leftImpact !== rightImpact) {
|
|
286
|
+
return rightImpact - leftImpact;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const leftGravity = left.risk.gravity ?? -1;
|
|
290
|
+
const rightGravity = right.risk.gravity ?? -1;
|
|
291
|
+
if (leftGravity !== rightGravity) {
|
|
292
|
+
return rightGravity - leftGravity;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (leftCoordination.exportsCount !== rightCoordination.exportsCount) {
|
|
296
|
+
return rightCoordination.exportsCount - leftCoordination.exportsCount;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return left.file.localeCompare(right.file);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function main() {
|
|
304
|
+
const top = parseTop(process.argv.slice(2));
|
|
305
|
+
assertBuiltArtifacts();
|
|
306
|
+
refreshCodemapArtifacts();
|
|
307
|
+
|
|
308
|
+
const { createConfiguredStorage } = await import(pathToFileURL(
|
|
309
|
+
path.join(projectRoot, 'dist', 'cli', 'storage-runtime.js')
|
|
310
|
+
).href);
|
|
311
|
+
const { GitHistoryService } = await import(pathToFileURL(
|
|
312
|
+
path.join(projectRoot, 'dist', 'orchestrator', 'history-risk-service.js')
|
|
313
|
+
).href);
|
|
314
|
+
|
|
315
|
+
const allSourceFiles = listSourceFiles(path.join(projectRoot, 'src'));
|
|
316
|
+
const shortlistBudget = Math.min(allSourceFiles.length, Math.max(top * 6, 12));
|
|
317
|
+
const { storage, loadedConfig } = await createConfiguredStorage(projectRoot);
|
|
318
|
+
const codeMap = loadCodeMap(loadedConfig.config.output);
|
|
319
|
+
const coordinationByFile = buildCoordinationSignals(codeMap);
|
|
320
|
+
const shortlistedFiles = buildShortlist(allSourceFiles, coordinationByFile, shortlistBudget).map((entry) => entry.file);
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const historyService = new GitHistoryService({
|
|
324
|
+
projectRoot,
|
|
325
|
+
storage,
|
|
326
|
+
});
|
|
327
|
+
const historyResult = await historyService.analyzeFiles(shortlistedFiles, {
|
|
328
|
+
maxFiles: shortlistedFiles.length,
|
|
329
|
+
persist: false,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const rankedSignals = rankSignals(historyResult.files, coordinationByFile).slice(0, top);
|
|
333
|
+
const report = rankedSignals.map((signal, index) => ({
|
|
334
|
+
rank: index + 1,
|
|
335
|
+
file: signal.file,
|
|
336
|
+
status: signal.diagnostics.status,
|
|
337
|
+
confidence: signal.diagnostics.confidence,
|
|
338
|
+
freshness: signal.diagnostics.freshness,
|
|
339
|
+
source: signal.diagnostics.source,
|
|
340
|
+
score: signal.risk.score,
|
|
341
|
+
level: signal.risk.level,
|
|
342
|
+
gravity: signal.risk.gravity,
|
|
343
|
+
impact: signal.risk.impact,
|
|
344
|
+
heat: signal.risk.heat,
|
|
345
|
+
tagSummary: summarizeTags(signal.timeline),
|
|
346
|
+
ownerSummary: summarizeOwners(signal.timeline),
|
|
347
|
+
riskFactors: signal.risk.riskFactors,
|
|
348
|
+
coordination: coordinationByFile.get(signal.file) ?? {
|
|
349
|
+
imports: 0,
|
|
350
|
+
dependents: 0,
|
|
351
|
+
exportsCount: 0,
|
|
352
|
+
observedBlastRadius: 0,
|
|
353
|
+
calibrationPriority: 0,
|
|
354
|
+
calibratedBlastRadius: 0,
|
|
355
|
+
},
|
|
356
|
+
}));
|
|
357
|
+
|
|
358
|
+
const baselineMatches = report
|
|
359
|
+
.map((entry) => entry.file)
|
|
360
|
+
.filter((file) => CALIBRATION_BASELINE.includes(file));
|
|
361
|
+
const containsCliIndex = baselineMatches.includes('src/cli/index.ts');
|
|
362
|
+
const calibrationPassed = containsCliIndex && baselineMatches.length >= 2;
|
|
363
|
+
|
|
364
|
+
console.log(JSON.stringify({
|
|
365
|
+
scannedSourceFiles: allSourceFiles.length,
|
|
366
|
+
shortlistBudget,
|
|
367
|
+
shortlistedFiles,
|
|
368
|
+
diagnostics: {
|
|
369
|
+
status: historyResult.diagnostics.status,
|
|
370
|
+
confidence: historyResult.diagnostics.confidence,
|
|
371
|
+
freshness: historyResult.diagnostics.freshness,
|
|
372
|
+
scopeMode: historyResult.diagnostics.scopeMode,
|
|
373
|
+
source: historyResult.diagnostics.source,
|
|
374
|
+
requiresPrecompute: historyResult.diagnostics.requiresPrecompute,
|
|
375
|
+
reasons: historyResult.diagnostics.reasons,
|
|
376
|
+
},
|
|
377
|
+
calibration: {
|
|
378
|
+
required: 'src/cli/index.ts',
|
|
379
|
+
preferredPool: CALIBRATION_BASELINE,
|
|
380
|
+
baselineMatches,
|
|
381
|
+
passed: calibrationPassed,
|
|
382
|
+
},
|
|
383
|
+
topRiskFiles: report,
|
|
384
|
+
}, null, 2));
|
|
385
|
+
|
|
386
|
+
if (!calibrationPassed) {
|
|
387
|
+
console.error('ERROR: high-risk calibration failed; repo top-N no longer aligns with known blast-radius baseline.');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
} finally {
|
|
391
|
+
await storage.close();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
await main();
|