@mmerterden/multi-agent-pipeline 12.7.0 → 12.9.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/CHANGELOG.md +207 -0
- package/install/_common.mjs +48 -0
- package/install/_dev-only-files.mjs +125 -5
- package/install/claude.mjs +14 -8
- package/install/copilot.mjs +5 -8
- package/package.json +17 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
- package/pipeline/lib/credential-store.sh +20 -0
- package/pipeline/multi-agent-refs/_account-picker.md +1 -1
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/_repo-picker.md +1 -1
- package/pipeline/multi-agent-refs/features/review-multi-repo.md +65 -0
- package/pipeline/multi-agent-refs/features/url-enrichment.md +93 -0
- package/pipeline/multi-agent-refs/phases/operations.md +28 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +3 -83
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +50 -62
- package/pipeline/schemas/prefs.schema.json +6 -0
- package/pipeline/scripts/_smoke-root.sh +61 -0
- package/pipeline/scripts/agent-guard.py +57 -3
- package/pipeline/scripts/audit-log.sh +25 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
- package/pipeline/eval/golden-tasks/README.md +0 -65
- package/pipeline/eval/intent-cases.json +0 -40
- package/pipeline/eval/run-metrics-fixture.json +0 -93
- package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
- package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
- package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
- package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
- package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
- package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
- package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
- package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
- package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
- package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
- package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
- package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
- package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
- package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
- package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
- package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
- package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
- package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
- package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
- package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
- package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
- package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
- package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
- package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
- package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
- package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
- package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
- package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
- package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
- package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
- package/pipeline/eval/triage/README.md +0 -54
- package/pipeline/scripts/benchmark-phase-0.sh +0 -128
- package/pipeline/scripts/check-md-links.mjs +0 -88
- package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
- package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
- package/pipeline/scripts/eval-intent.mjs +0 -107
- package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
- package/pipeline/scripts/eval-triage.mjs +0 -171
- package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
- package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
- package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
- package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
- package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
- package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
- package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
- package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
- package/pipeline/scripts/lint-skills.mjs +0 -154
- package/pipeline/scripts/run-smokes.mjs +0 -130
- package/pipeline/scripts/scorecard.mjs +0 -258
- package/pipeline/scripts/smoke-add-detail.sh +0 -137
- package/pipeline/scripts/smoke-agent-guard.sh +0 -74
- package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
- package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
- package/pipeline/scripts/smoke-ask-choice.sh +0 -42
- package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
- package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
- package/pipeline/scripts/smoke-changelog-version.sh +0 -47
- package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
- package/pipeline/scripts/smoke-channels-flow.sh +0 -130
- package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
- package/pipeline/scripts/smoke-clarify.sh +0 -148
- package/pipeline/scripts/smoke-command-inventory.sh +0 -81
- package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
- package/pipeline/scripts/smoke-community-gates.sh +0 -75
- package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
- package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
- package/pipeline/scripts/smoke-context-budget.sh +0 -72
- package/pipeline/scripts/smoke-cost-budget.sh +0 -70
- package/pipeline/scripts/smoke-cost-summary.sh +0 -139
- package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
- package/pipeline/scripts/smoke-description-tr.sh +0 -82
- package/pipeline/scripts/smoke-dev-critic.sh +0 -144
- package/pipeline/scripts/smoke-diff-explain.sh +0 -147
- package/pipeline/scripts/smoke-diff-risk.sh +0 -190
- package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
- package/pipeline/scripts/smoke-eval-live.sh +0 -136
- package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
- package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
- package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
- package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
- package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
- package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
- package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
- package/pipeline/scripts/smoke-generate-issue.sh +0 -120
- package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
- package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
- package/pipeline/scripts/smoke-install-layout.sh +0 -248
- package/pipeline/scripts/smoke-intent-guard.sh +0 -86
- package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
- package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
- package/pipeline/scripts/smoke-keychain.sh +0 -158
- package/pipeline/scripts/smoke-language-axis.sh +0 -109
- package/pipeline/scripts/smoke-learning-curve.sh +0 -61
- package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
- package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
- package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
- package/pipeline/scripts/smoke-md-links.sh +0 -8
- package/pipeline/scripts/smoke-md2confluence.sh +0 -126
- package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
- package/pipeline/scripts/smoke-migrate-state.sh +0 -102
- package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
- package/pipeline/scripts/smoke-model-fallback.sh +0 -89
- package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
- package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
- package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
- package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
- package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
- package/pipeline/scripts/smoke-pack-contents.sh +0 -140
- package/pipeline/scripts/smoke-pat-audit.sh +0 -128
- package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
- package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
- package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
- package/pipeline/scripts/smoke-phase-banner.sh +0 -101
- package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
- package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
- package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
- package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
- package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
- package/pipeline/scripts/smoke-plan-safety.sh +0 -139
- package/pipeline/scripts/smoke-plan-todos.sh +0 -196
- package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
- package/pipeline/scripts/smoke-pre-commit.sh +0 -170
- package/pipeline/scripts/smoke-pref-migration.sh +0 -226
- package/pipeline/scripts/smoke-prefs-language.sh +0 -134
- package/pipeline/scripts/smoke-progress-contract.sh +0 -127
- package/pipeline/scripts/smoke-prune-logs.sh +0 -137
- package/pipeline/scripts/smoke-purge.sh +0 -138
- package/pipeline/scripts/smoke-push-retry.sh +0 -75
- package/pipeline/scripts/smoke-repo-map.sh +0 -300
- package/pipeline/scripts/smoke-review-readiness.sh +0 -92
- package/pipeline/scripts/smoke-review-watch.sh +0 -146
- package/pipeline/scripts/smoke-routines.sh +0 -84
- package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
- package/pipeline/scripts/smoke-run-metrics.sh +0 -50
- package/pipeline/scripts/smoke-search.sh +0 -187
- package/pipeline/scripts/smoke-shadow-git.sh +0 -224
- package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
- package/pipeline/scripts/smoke-skill-language.sh +0 -83
- package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
- package/pipeline/scripts/smoke-skill-scan.sh +0 -198
- package/pipeline/scripts/smoke-source-parity.sh +0 -85
- package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
- package/pipeline/scripts/smoke-sync-parity.sh +0 -92
- package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
- package/pipeline/scripts/smoke-telemetry.sh +0 -147
- package/pipeline/scripts/smoke-test-gap.sh +0 -183
- package/pipeline/scripts/smoke-token-budget.sh +0 -67
- package/pipeline/scripts/smoke-token-preflight.sh +0 -82
- package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
- package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
- package/pipeline/scripts/smoke-triage-memory.sh +0 -174
- package/pipeline/scripts/smoke-update-check.sh +0 -135
- package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
- package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
- package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
- package/pipeline/scripts/smoke-validator-gates.sh +0 -164
- package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
- package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
- package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
- package/pipeline/scripts/smoke-work-summary.sh +0 -163
- package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
- package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
- package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
- package/pipeline/scripts/smoke-write-state.sh +0 -159
- package/pipeline/scripts/sync-parity-check.sh +0 -135
- package/pipeline/scripts/test-gap-rules/android.json +0 -25
- package/pipeline/scripts/test-gap-rules/ios.json +0 -34
- package/pipeline/scripts/test-gap-rules/node.json +0 -29
- package/pipeline/scripts/test-gap-rules/python.json +0 -25
- package/pipeline/scripts/validate-schemas.mjs +0 -88
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# smoke-no-mcp-in-dev-phases.sh
|
|
4
|
-
#
|
|
5
|
-
# Gate: pipeline run telemetry must not contain MCP calls outside Phase 1
|
|
6
|
-
# (Analysis). Per Locked decision 30, MCP usage is permitted only during
|
|
7
|
-
# Phase 1; any entry in state.telemetry.mcpCalls[] with phase >= 2 is a
|
|
8
|
-
# violation.
|
|
9
|
-
#
|
|
10
|
-
# Two modes.
|
|
11
|
-
#
|
|
12
|
-
# CHECK MODE (--state-file <path>): audit that one state file.
|
|
13
|
-
# - telemetry.mcpCalls field absent -> PASS, exit 0 (older run, no telemetry)
|
|
14
|
-
# - All entries phase < 2 -> PASS, exit 0
|
|
15
|
-
# - One or more violations -> FAIL summary on stdout, exit 1
|
|
16
|
-
# - Missing / corrupt file -> exit 2
|
|
17
|
-
#
|
|
18
|
-
# SELF-TEST MODE (no arguments): drive check mode over fixtures that cover
|
|
19
|
-
# every verdict, then audit the live state file if one happens to exist.
|
|
20
|
-
#
|
|
21
|
-
# Why: with no --state-file this script used to print "no run to check" and
|
|
22
|
-
# exit 0. On a clean clone, on CI, and on any machine that had not just
|
|
23
|
-
# finished a pipeline run - which is nearly always - the gate for Locked
|
|
24
|
-
# decision 30 therefore asserted nothing while reporting success. A gate whose
|
|
25
|
-
# green tick means "I had nothing to look at" is indistinguishable from one
|
|
26
|
-
# that passed, and this suite had three of those.
|
|
27
|
-
#
|
|
28
|
-
# stdout: human-readable summary only. stderr: empty in normal operation.
|
|
29
|
-
|
|
30
|
-
set -euo pipefail
|
|
31
|
-
|
|
32
|
-
STATE_FILE="${HOME}/.claude/state/run/latest.json"
|
|
33
|
-
STATE_FILE_EXPLICIT=0
|
|
34
|
-
|
|
35
|
-
while [ "$#" -gt 0 ]; do
|
|
36
|
-
case "$1" in
|
|
37
|
-
--state-file)
|
|
38
|
-
shift
|
|
39
|
-
if [ "$#" -eq 0 ]; then
|
|
40
|
-
echo "FAIL: --state-file requires a path argument"
|
|
41
|
-
exit 2
|
|
42
|
-
fi
|
|
43
|
-
STATE_FILE="$1"
|
|
44
|
-
STATE_FILE_EXPLICIT=1
|
|
45
|
-
shift
|
|
46
|
-
;;
|
|
47
|
-
--state-file=*)
|
|
48
|
-
STATE_FILE="${1#--state-file=}"
|
|
49
|
-
STATE_FILE_EXPLICIT=1
|
|
50
|
-
shift
|
|
51
|
-
;;
|
|
52
|
-
-h|--help)
|
|
53
|
-
echo "Usage: $0 [--state-file <path>]"
|
|
54
|
-
echo ""
|
|
55
|
-
echo "No arguments: run the fixture self-test suite."
|
|
56
|
-
echo "Default state file when checking live: ~/.claude/state/run/latest.json"
|
|
57
|
-
exit 0
|
|
58
|
-
;;
|
|
59
|
-
*)
|
|
60
|
-
echo "FAIL: unknown argument: $1"
|
|
61
|
-
exit 2
|
|
62
|
-
;;
|
|
63
|
-
esac
|
|
64
|
-
done
|
|
65
|
-
|
|
66
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
67
|
-
echo "FAIL: jq is required but not installed"
|
|
68
|
-
exit 2
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
# ── self-test mode ─────────────────────────────────────────────────────────
|
|
72
|
-
if [ "$STATE_FILE_EXPLICIT" -eq 0 ]; then
|
|
73
|
-
SELF="$0"
|
|
74
|
-
TMP=$(mktemp -d)
|
|
75
|
-
trap 'rm -rf "$TMP"' EXIT
|
|
76
|
-
PASS=0
|
|
77
|
-
FAIL=0
|
|
78
|
-
|
|
79
|
-
expect() {
|
|
80
|
-
local name="$1" want="$2" file="$3"
|
|
81
|
-
local got=0
|
|
82
|
-
bash "$SELF" --state-file "$file" >/dev/null 2>&1 || got=$?
|
|
83
|
-
if [ "$got" -eq "$want" ]; then
|
|
84
|
-
PASS=$((PASS + 1)); echo " ✓ $name (exit $got)"
|
|
85
|
-
else
|
|
86
|
-
FAIL=$((FAIL + 1)); echo " ✗ $name (exit $got, expected $want)"
|
|
87
|
-
fi
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
echo "→ Locked decision 30 gate, fixture self-test"
|
|
91
|
-
|
|
92
|
-
printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"mcp__figma__get_design_context","phase":1}]}}' \
|
|
93
|
-
> "$TMP/phase1-only.json"
|
|
94
|
-
expect "MCP call in Phase 1 passes" 0 "$TMP/phase1-only.json"
|
|
95
|
-
|
|
96
|
-
printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"mcp__figma__get_design_context","phase":3}]}}' \
|
|
97
|
-
> "$TMP/phase3.json"
|
|
98
|
-
expect "MCP call in Phase 3 is a violation" 1 "$TMP/phase3.json"
|
|
99
|
-
|
|
100
|
-
printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"a","phase":1},{"tool":"b","phase":2}]}}' \
|
|
101
|
-
> "$TMP/mixed.json"
|
|
102
|
-
expect "one violation among clean entries still fails" 1 "$TMP/mixed.json"
|
|
103
|
-
|
|
104
|
-
printf '%s' '{"telemetry":{"mcpCalls":[]}}' > "$TMP/empty.json"
|
|
105
|
-
expect "empty call list passes" 0 "$TMP/empty.json"
|
|
106
|
-
|
|
107
|
-
printf '%s' '{"telemetry":{}}' > "$TMP/no-field.json"
|
|
108
|
-
expect "absent telemetry.mcpCalls passes (older state format)" 0 "$TMP/no-field.json"
|
|
109
|
-
|
|
110
|
-
printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"a"}]}}' > "$TMP/no-phase.json"
|
|
111
|
-
expect "entry with no phase defaults to Phase 0 and passes" 0 "$TMP/no-phase.json"
|
|
112
|
-
|
|
113
|
-
printf '%s' 'not json at all' > "$TMP/corrupt.json"
|
|
114
|
-
expect "corrupt state is an error, never a silent pass" 2 "$TMP/corrupt.json"
|
|
115
|
-
|
|
116
|
-
expect "missing state file is an error in check mode" 2 "$TMP/does-not-exist.json"
|
|
117
|
-
|
|
118
|
-
# The live run, when there is one. Absence is not a failure here - the
|
|
119
|
-
# fixtures above already proved the gate works.
|
|
120
|
-
if [ -f "$STATE_FILE" ]; then
|
|
121
|
-
echo "→ live state file"
|
|
122
|
-
if bash "$SELF" --state-file "$STATE_FILE"; then
|
|
123
|
-
PASS=$((PASS + 1)); echo " ✓ live run clean"
|
|
124
|
-
else
|
|
125
|
-
FAIL=$((FAIL + 1)); echo " ✗ live run has violations"
|
|
126
|
-
fi
|
|
127
|
-
else
|
|
128
|
-
echo "→ live state file: none at $STATE_FILE (fixtures already exercised every path)"
|
|
129
|
-
fi
|
|
130
|
-
|
|
131
|
-
echo ""
|
|
132
|
-
echo "══ smoke-no-mcp-in-dev-phases: $PASS passed, $FAIL failed ══"
|
|
133
|
-
[ "$FAIL" -gt 0 ] && exit 1 || exit 0
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# ── check mode ─────────────────────────────────────────────────────────────
|
|
137
|
-
if [ ! -f "$STATE_FILE" ]; then
|
|
138
|
-
echo "FAIL: state file not found at $STATE_FILE"
|
|
139
|
-
exit 2
|
|
140
|
-
fi
|
|
141
|
-
|
|
142
|
-
# Validate JSON parse first; corrupt state cannot be silently passed.
|
|
143
|
-
if ! jq empty "$STATE_FILE" >/dev/null 2>&1; then
|
|
144
|
-
echo "FAIL: state file is not valid JSON: $STATE_FILE"
|
|
145
|
-
exit 2
|
|
146
|
-
fi
|
|
147
|
-
|
|
148
|
-
# If telemetry.mcpCalls is absent or not an array, this is an older state
|
|
149
|
-
# format that did not record MCP usage. PASS by contract.
|
|
150
|
-
HAS_FIELD=$(jq -r '
|
|
151
|
-
if (.telemetry // empty) | type == "object"
|
|
152
|
-
then
|
|
153
|
-
if (.telemetry.mcpCalls // empty) | type == "array" then "yes" else "no" end
|
|
154
|
-
else "no" end
|
|
155
|
-
' "$STATE_FILE")
|
|
156
|
-
|
|
157
|
-
if [ "$HAS_FIELD" != "yes" ]; then
|
|
158
|
-
echo "PASS: telemetry.mcpCalls field absent - no MCP usage recorded"
|
|
159
|
-
exit 0
|
|
160
|
-
fi
|
|
161
|
-
|
|
162
|
-
# Count total entries and violations (phase >= 2).
|
|
163
|
-
TOTAL=$(jq -r '.telemetry.mcpCalls | length' "$STATE_FILE")
|
|
164
|
-
VIOLATIONS=$(jq -r '
|
|
165
|
-
.telemetry.mcpCalls
|
|
166
|
-
| map(select((.phase // 0) >= 2))
|
|
167
|
-
| length
|
|
168
|
-
' "$STATE_FILE")
|
|
169
|
-
|
|
170
|
-
if [ "$VIOLATIONS" -eq 0 ]; then
|
|
171
|
-
if [ "$TOTAL" -eq 0 ]; then
|
|
172
|
-
echo "PASS: no MCP calls outside Phase 1 (Analysis) - 0 entries"
|
|
173
|
-
else
|
|
174
|
-
echo "PASS: no MCP calls outside Phase 1 (Analysis) - ${TOTAL} entries, all in Phase 1"
|
|
175
|
-
fi
|
|
176
|
-
exit 0
|
|
177
|
-
fi
|
|
178
|
-
|
|
179
|
-
# Emit FAIL summary with up to first 5 offenders.
|
|
180
|
-
echo "FAIL: ${VIOLATIONS} MCP call(s) outside Phase 1 (Analysis) - Locked decision 30 violation"
|
|
181
|
-
echo ""
|
|
182
|
-
echo "First $([ "$VIOLATIONS" -lt 5 ] && echo "$VIOLATIONS" || echo 5) violation(s):"
|
|
183
|
-
|
|
184
|
-
jq -r '
|
|
185
|
-
.telemetry.mcpCalls
|
|
186
|
-
| map(select((.phase // 0) >= 2))
|
|
187
|
-
| .[0:5]
|
|
188
|
-
| .[]
|
|
189
|
-
| " FAIL: MCP call \(.tool // "<unknown>") in Phase \(.phase) at \(.timestamp // "<no timestamp>") - Locked decision 30 violation"
|
|
190
|
-
' "$STATE_FILE"
|
|
191
|
-
|
|
192
|
-
echo ""
|
|
193
|
-
echo "Total violations: ${VIOLATIONS} of ${TOTAL} entries"
|
|
194
|
-
exit 1
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# smoke-no-token-prompt.sh
|
|
4
|
-
#
|
|
5
|
-
# Asserts the pipeline never asks the user to type a token in chat. Tokens
|
|
6
|
-
# are resolved via credential-store.sh; missing/expired tokens trigger the
|
|
7
|
-
# Setup Wizard, not an inline prompt. See refs/keychain.md Rule 1.
|
|
8
|
-
#
|
|
9
|
-
# Greps shell scripts, markdown adapters, and orchestrator commands for
|
|
10
|
-
# hardcoded prompt patterns. Allowlist for legitimate setup wizard prompts.
|
|
11
|
-
|
|
12
|
-
set -euo pipefail
|
|
13
|
-
|
|
14
|
-
SELF_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
15
|
-
PIPELINE_ROOT=$(cd "$SELF_DIR/.." && pwd)
|
|
16
|
-
|
|
17
|
-
cd "$PIPELINE_ROOT"
|
|
18
|
-
|
|
19
|
-
FAIL=0
|
|
20
|
-
|
|
21
|
-
# Pattern: anything that looks like the script asking for a token interactively.
|
|
22
|
-
# Acceptable: setup wizard explicitly tagged with `# allow: token-prompt`.
|
|
23
|
-
PATTERNS=(
|
|
24
|
-
"read -rp.*[Tt]oken"
|
|
25
|
-
"read -rp.*[Aa]pi.[Kk]ey"
|
|
26
|
-
"read -rp.*[Pp]aste.*token"
|
|
27
|
-
"read -rp.*[Ee]nter.*token"
|
|
28
|
-
'echo "Enter your.*[Tt]oken'
|
|
29
|
-
'echo "Paste.*[Tt]oken'
|
|
30
|
-
"AskUserQuestion.*[Tt]oken"
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
# Files in scope: shell scripts + adapter markdown.
|
|
34
|
-
TARGET_GLOB=(
|
|
35
|
-
"scripts/*.sh"
|
|
36
|
-
"scripts/*.mjs"
|
|
37
|
-
"multi-agent-refs/channels/*.md"
|
|
38
|
-
"multi-agent-refs/phases/*.md"
|
|
39
|
-
"commands/multi-agent/dev/SKILL.md"
|
|
40
|
-
"commands/multi-agent/dev-autopilot/SKILL.md"
|
|
41
|
-
"commands/multi-agent/autopilot/SKILL.md"
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
# Build a single grep invocation: all patterns via repeated -e flags,
|
|
45
|
-
# all target globs expanded once. Drops 7x7=49 grep subprocesses down to 1.
|
|
46
|
-
GREP_PATTERN_ARGS=()
|
|
47
|
-
for pattern in "${PATTERNS[@]}"; do
|
|
48
|
-
GREP_PATTERN_ARGS+=(-e "$pattern")
|
|
49
|
-
done
|
|
50
|
-
|
|
51
|
-
# Expand all target globs to actual paths once. Globs that don't match are
|
|
52
|
-
# silently dropped so the single grep call sees only existing files.
|
|
53
|
-
TARGET_PATHS=()
|
|
54
|
-
for glob in "${TARGET_GLOB[@]}"; do
|
|
55
|
-
for path in pipeline/$glob; do
|
|
56
|
-
[ -e "$path" ] && TARGET_PATHS+=("$path")
|
|
57
|
-
done
|
|
58
|
-
done
|
|
59
|
-
|
|
60
|
-
if [ "${#TARGET_PATHS[@]}" -gt 0 ]; then
|
|
61
|
-
while IFS= read -r match; do
|
|
62
|
-
[ -z "$match" ] && continue
|
|
63
|
-
file="${match%%:*}"
|
|
64
|
-
# Allowlist: setup.md and setup-related files explicitly handle token entry
|
|
65
|
-
# via the inline Token Save Flow (clipboard-based, not chat).
|
|
66
|
-
case "$file" in
|
|
67
|
-
*/setup.md|*setup-token-flow*|*credential-store*) continue ;;
|
|
68
|
-
esac
|
|
69
|
-
# Allowlist: explicit `# allow: token-prompt` annotation on the line.
|
|
70
|
-
case "$match" in
|
|
71
|
-
*'# allow: token-prompt'*) continue ;;
|
|
72
|
-
esac
|
|
73
|
-
echo "FAIL [smoke-no-token-prompt]: token prompt found in $file"
|
|
74
|
-
echo " $match"
|
|
75
|
-
FAIL=$((FAIL + 1))
|
|
76
|
-
done < <(grep -rEn "${GREP_PATTERN_ARGS[@]}" "${TARGET_PATHS[@]}" 2>/dev/null || true)
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
if [ "$FAIL" -gt 0 ]; then
|
|
80
|
-
echo ""
|
|
81
|
-
echo "smoke-no-token-prompt: $FAIL violation(s). Tokens must be resolved via credential-store.sh; see refs/keychain.md Rule 1."
|
|
82
|
-
exit 1
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
echo "smoke-no-token-prompt: ok"
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# smoke-own-punctuation.sh - the punctuation rule applies to this repo's own
|
|
4
|
-
# shipped files, not only to the documents it generates.
|
|
5
|
-
#
|
|
6
|
-
# validate-analysis-doc.mjs has enforced the locked policy (no em-dash, en-dash,
|
|
7
|
-
# ellipsis or section sign) on generated analysis documents for a long time,
|
|
8
|
-
# and smoke-generate-issue.sh / smoke-review-readiness.sh enforce it on issue
|
|
9
|
-
# and review payloads. Nothing applied it to the pipeline's own tree, so 77
|
|
10
|
-
# shipped files carried 719 em-dashes, 10 en-dashes and 24 ellipses while the
|
|
11
|
-
# rule was described as enforced.
|
|
12
|
-
#
|
|
13
|
-
# ALLOWED holds the files that carry these characters as data rather than
|
|
14
|
-
# prose: the detector's own table and the grep patterns that look for them.
|
|
15
|
-
# Stripping those would disable the very checks this rule depends on. Every
|
|
16
|
-
# entry needs a reason, and the list is expected to stay this short.
|
|
17
|
-
#
|
|
18
|
-
# Exit codes: 0=clean, 1=violations
|
|
19
|
-
|
|
20
|
-
set -euo pipefail
|
|
21
|
-
|
|
22
|
-
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
23
|
-
cd "$REPO_ROOT"
|
|
24
|
-
|
|
25
|
-
# file path -> why the characters are legitimately present
|
|
26
|
-
ALLOWED_PATHS="
|
|
27
|
-
pipeline/scripts/validate-analysis-doc.mjs|the detector's own table of banned characters
|
|
28
|
-
pipeline/scripts/smoke-generate-issue.sh|greps for the banned characters
|
|
29
|
-
pipeline/scripts/smoke-review-readiness.sh|greps for the banned characters
|
|
30
|
-
pipeline/scripts/smoke-md2confluence.sh|dirty-input fixture plus the forbidden list
|
|
31
|
-
pipeline/scripts/smoke-own-punctuation.sh|this file names the characters it bans
|
|
32
|
-
pipeline/lib/md2confluence-v3.py|the converter's codepoint-to-ASCII mapping table
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
# The same characters written as escapes. prefs.schema.json carried 50 of them
|
|
36
|
-
# in its descriptions and was invisible to a search for the literal character,
|
|
37
|
-
# so the first version of this gate passed it. A rule that only sees one
|
|
38
|
-
# encoding of what it bans is not enforcing the rule.
|
|
39
|
-
ESCAPED='\\u2014|\\u2013|\\u2026|\\u00a7'
|
|
40
|
-
|
|
41
|
-
is_allowed() {
|
|
42
|
-
printf '%s\n' "$ALLOWED_PATHS" | grep -q "^$1|"
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
PASS=0
|
|
46
|
-
FAIL=0
|
|
47
|
-
pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
|
|
48
|
-
fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
|
|
49
|
-
|
|
50
|
-
echo "→ 1. no em-dash, en-dash, ellipsis or section sign in shipped files"
|
|
51
|
-
|
|
52
|
-
# CHANGELOG history is left as written; rewriting released notes to satisfy a
|
|
53
|
-
# later style rule would falsify the record.
|
|
54
|
-
VIOLATIONS=""
|
|
55
|
-
while IFS= read -r f; do
|
|
56
|
-
case "$f" in CHANGELOG*.md) continue ;; esac
|
|
57
|
-
is_allowed "$f" && continue
|
|
58
|
-
VIOLATIONS="${VIOLATIONS}${f}\n"
|
|
59
|
-
done < <(git grep -lI -e $'—' -e $'–' -e $'…' -e $'§' -- . 2>/dev/null || true)
|
|
60
|
-
|
|
61
|
-
if [ -z "$VIOLATIONS" ]; then
|
|
62
|
-
pass "no banned punctuation outside the allowlist"
|
|
63
|
-
else
|
|
64
|
-
COUNT=$(printf '%b' "$VIOLATIONS" | grep -c . || true)
|
|
65
|
-
fail "$COUNT file(s) carry banned punctuation"
|
|
66
|
-
printf '%b' "$VIOLATIONS" | sed 's/^/ /'
|
|
67
|
-
echo " fix: plain ASCII - or : or parentheses; 'section 4' rather than the sign"
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
echo "→ 2. same characters written as unicode escapes"
|
|
71
|
-
ESC_VIOLATIONS=""
|
|
72
|
-
while IFS= read -r f; do
|
|
73
|
-
case "$f" in CHANGELOG*.md) continue ;; esac
|
|
74
|
-
is_allowed "$f" && continue
|
|
75
|
-
ESC_VIOLATIONS="${ESC_VIOLATIONS}${f}\n"
|
|
76
|
-
done < <(git grep -lI -E "$ESCAPED" -- . 2>/dev/null || true)
|
|
77
|
-
|
|
78
|
-
if [ -z "$ESC_VIOLATIONS" ]; then
|
|
79
|
-
pass "no escaped forms outside the allowlist"
|
|
80
|
-
else
|
|
81
|
-
COUNT=$(printf '%b' "$ESC_VIOLATIONS" | grep -c . || true)
|
|
82
|
-
fail "$COUNT file(s) carry the banned characters as unicode escapes"
|
|
83
|
-
printf '%b' "$ESC_VIOLATIONS" | sed 's/^/ /'
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
echo "→ 3. every allowlist entry still exists and still needs the exemption"
|
|
87
|
-
while IFS='|' read -r path reason; do
|
|
88
|
-
[ -z "$path" ] && continue
|
|
89
|
-
if [ ! -f "$path" ]; then
|
|
90
|
-
fail "allowlisted path no longer exists: $path"
|
|
91
|
-
elif ! grep -qI -e $'—' -e $'–' -e $'…' -e $'§' "$path" 2>/dev/null \
|
|
92
|
-
&& ! grep -qIE "$ESCAPED" "$path" 2>/dev/null; then
|
|
93
|
-
# Either encoding justifies the entry: md2confluence-v3.py holds them as
|
|
94
|
-
# escapes, the detector table holds them literally.
|
|
95
|
-
fail "$path is allowlisted ($reason) but is now clean - drop it from the list"
|
|
96
|
-
else
|
|
97
|
-
pass "allowlist justified: ${path##*/} ($reason)"
|
|
98
|
-
fi
|
|
99
|
-
done < <(printf '%s\n' "$ALLOWED_PATHS" | grep '|')
|
|
100
|
-
|
|
101
|
-
echo ""
|
|
102
|
-
echo "══ smoke-own-punctuation: $PASS passed, $FAIL failed ══"
|
|
103
|
-
[ "$FAIL" -gt 0 ] && exit 1 || exit 0
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# smoke-pack-contents.sh - guard what `npm publish` would actually ship.
|
|
4
|
-
#
|
|
5
|
-
# 1. Deny patterns: package.json files[] carries negations (`!...`) for
|
|
6
|
-
# dev-only / personal-data files. The published tarball must contain
|
|
7
|
-
# no file matching any of them. Patterns are read from package.json at
|
|
8
|
-
# runtime (via node), so the smoke never goes stale when the deny list
|
|
9
|
-
# changes.
|
|
10
|
-
# 2. File-count band: the total packed file count is compared against the
|
|
11
|
-
# committed fixture pipeline/scripts/fixtures/pack-expected-count.txt.
|
|
12
|
-
# Drift beyond +-15 percent fails - that magnitude means the files[]
|
|
13
|
-
# globs picked up (or dropped) a whole subtree, which deserves a human
|
|
14
|
-
# look. Refresh the fixture after reviewing:
|
|
15
|
-
# npm pack --dry-run --json 2>/dev/null \
|
|
16
|
-
# | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s)[0].files.length))' \
|
|
17
|
-
# > pipeline/scripts/fixtures/pack-expected-count.txt
|
|
18
|
-
# 3. publint (optional): linted via `npx --no-install` so it only runs
|
|
19
|
-
# when the package is already installed - never a network fetch.
|
|
20
|
-
#
|
|
21
|
-
# Exit codes: 0=pass, 1=violation, 2=environment broken
|
|
22
|
-
|
|
23
|
-
set -uo pipefail
|
|
24
|
-
|
|
25
|
-
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
26
|
-
FIXTURE="$REPO_ROOT/pipeline/scripts/fixtures/pack-expected-count.txt"
|
|
27
|
-
|
|
28
|
-
command -v npm >/dev/null 2>&1 || { echo "FAIL: npm not on PATH"; exit 2; }
|
|
29
|
-
command -v node >/dev/null 2>&1 || { echo "FAIL: node not on PATH"; exit 2; }
|
|
30
|
-
|
|
31
|
-
PASS=0
|
|
32
|
-
FAIL=0
|
|
33
|
-
pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
|
|
34
|
-
fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
|
|
35
|
-
|
|
36
|
-
cleanup() { [ -n "${TMP:-}" ] && rm -rf "$TMP"; }
|
|
37
|
-
trap cleanup EXIT
|
|
38
|
-
TMP=$(mktemp -d)
|
|
39
|
-
|
|
40
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
41
|
-
echo "→ 1. npm pack --dry-run --json"
|
|
42
|
-
if ! (cd "$REPO_ROOT" && npm pack --dry-run --json 2>/dev/null > "$TMP/pack.json"); then
|
|
43
|
-
echo "FAIL: npm pack --dry-run failed"
|
|
44
|
-
exit 2
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# Deny check runs in node so the patterns come straight from package.json
|
|
48
|
-
# files[] negations - never hardcoded here.
|
|
49
|
-
cat > "$TMP/check.mjs" <<'MJS'
|
|
50
|
-
import { readFileSync } from "node:fs";
|
|
51
|
-
|
|
52
|
-
const [packJsonPath, pkgJsonPath] = process.argv.slice(2);
|
|
53
|
-
const packed = JSON.parse(readFileSync(packJsonPath, "utf-8"))[0].files.map((f) => f.path);
|
|
54
|
-
const files = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).files || [];
|
|
55
|
-
const denies = files.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
|
|
56
|
-
|
|
57
|
-
// Minimal glob -> regex: '**' spans path separators, '*' does not.
|
|
58
|
-
const toRegex = (glob) => {
|
|
59
|
-
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
60
|
-
const body = escaped
|
|
61
|
-
.split("**")
|
|
62
|
-
.map((part) => part.replace(/\*/g, "[^/]*"))
|
|
63
|
-
.join(".*");
|
|
64
|
-
return new RegExp("^" + body + "$");
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const regexes = denies.map((d) => [d, toRegex(d)]);
|
|
68
|
-
let bad = 0;
|
|
69
|
-
for (const path of packed) {
|
|
70
|
-
for (const [glob, re] of regexes) {
|
|
71
|
-
if (re.test(path)) {
|
|
72
|
-
console.log(`DENY ${path} (matches !${glob})`);
|
|
73
|
-
bad++;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
console.log(`DENYCOUNT ${bad}`);
|
|
78
|
-
console.log(`PATTERNS ${denies.length}`);
|
|
79
|
-
console.log(`COUNT ${packed.length}`);
|
|
80
|
-
MJS
|
|
81
|
-
|
|
82
|
-
CHECK=$(node "$TMP/check.mjs" "$TMP/pack.json" "$REPO_ROOT/package.json")
|
|
83
|
-
COUNT=$(echo "$CHECK" | awk '/^COUNT /{print $2}')
|
|
84
|
-
DENYCOUNT=$(echo "$CHECK" | awk '/^DENYCOUNT /{print $2}')
|
|
85
|
-
PATTERNS=$(echo "$CHECK" | awk '/^PATTERNS /{print $2}')
|
|
86
|
-
|
|
87
|
-
if [ -z "$COUNT" ] || [ -z "$DENYCOUNT" ]; then
|
|
88
|
-
echo "FAIL: deny-pattern checker produced no result (node error?)"
|
|
89
|
-
exit 2
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
pass "pack dry-run produced $COUNT files ($PATTERNS deny patterns loaded from package.json)"
|
|
93
|
-
|
|
94
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
95
|
-
echo "→ 2. deny patterns (files[] negations must not ship)"
|
|
96
|
-
if [ "$DENYCOUNT" -eq 0 ]; then
|
|
97
|
-
pass "no packed file matches a files[] negation"
|
|
98
|
-
else
|
|
99
|
-
fail "$DENYCOUNT packed file(s) match dev-only/personal-data exclusions:"
|
|
100
|
-
echo "$CHECK" | grep '^DENY ' | sed 's/^/ /'
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
104
|
-
echo "→ 3. file-count band vs fixture (+-15 percent)"
|
|
105
|
-
if [ ! -f "$FIXTURE" ]; then
|
|
106
|
-
fail "fixture missing: $FIXTURE - create it from the refresh command in this script's header"
|
|
107
|
-
else
|
|
108
|
-
EXPECTED=$(tr -cd '0-9' < "$FIXTURE")
|
|
109
|
-
if [ -z "$EXPECTED" ] || [ "$EXPECTED" -eq 0 ]; then
|
|
110
|
-
fail "fixture unreadable or zero: $FIXTURE"
|
|
111
|
-
else
|
|
112
|
-
DIFF=$((COUNT - EXPECTED))
|
|
113
|
-
[ "$DIFF" -lt 0 ] && DIFF=$((-DIFF))
|
|
114
|
-
LIMIT=$((EXPECTED * 15 / 100))
|
|
115
|
-
if [ "$DIFF" -le "$LIMIT" ]; then
|
|
116
|
-
pass "count $COUNT within +-15 percent of expected $EXPECTED (drift $DIFF, limit $LIMIT)"
|
|
117
|
-
else
|
|
118
|
-
fail "count $COUNT drifted beyond +-15 percent of expected $EXPECTED (drift $DIFF, limit $LIMIT)"
|
|
119
|
-
echo " Review what the files[] globs now include, then refresh the fixture"
|
|
120
|
-
echo " (refresh command is in this script's header comment)."
|
|
121
|
-
fi
|
|
122
|
-
fi
|
|
123
|
-
fi
|
|
124
|
-
|
|
125
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
126
|
-
echo "→ 4. publint (optional, npx --no-install only)"
|
|
127
|
-
if (cd "$REPO_ROOT" && npx --no-install publint --help >/dev/null 2>&1); then
|
|
128
|
-
if OUT=$(cd "$REPO_ROOT" && npx --no-install publint 2>&1); then
|
|
129
|
-
pass "publint: no errors"
|
|
130
|
-
else
|
|
131
|
-
fail "publint reported errors"
|
|
132
|
-
echo "$OUT" | sed 's/^/ /'
|
|
133
|
-
fi
|
|
134
|
-
else
|
|
135
|
-
echo " ↷ SKIP: publint not installed (npx --no-install found no local package)"
|
|
136
|
-
fi
|
|
137
|
-
|
|
138
|
-
echo ""
|
|
139
|
-
echo "══ smoke-pack-contents: $PASS passed, $FAIL failed ══"
|
|
140
|
-
[ "$FAIL" -gt 0 ] && exit 1 || exit 0
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-pat-audit.sh - exercises audit-log.sh + audit-log-rotate.sh and
|
|
3
|
-
# asserts the section18.2 contract:
|
|
4
|
-
#
|
|
5
|
-
# • Every PAT lookup writes a structured event to audit.jsonl
|
|
6
|
-
# • Repo URLs are SHA-256 hashed (no plaintext)
|
|
7
|
-
# • Identity is logged by name (NOT anonymized - user's own debug)
|
|
8
|
-
# • Audit log is gitignored
|
|
9
|
-
# • Daily rotation script preserves history (gzipped) + drops >30d archives
|
|
10
|
-
|
|
11
|
-
set -uo pipefail
|
|
12
|
-
|
|
13
|
-
AUDIT_FILE="$HOME/.claude/logs/multi-agent/audit.jsonl"
|
|
14
|
-
TMP_AUDIT="$(mktemp -d -t ma-audit-smoke-XXXX)"
|
|
15
|
-
TMP_FILE="$TMP_AUDIT/audit.jsonl"
|
|
16
|
-
trap 'rm -rf "$TMP_AUDIT"' EXIT
|
|
17
|
-
|
|
18
|
-
PASS=0; FAIL=0
|
|
19
|
-
pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
20
|
-
fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
|
|
21
|
-
|
|
22
|
-
LOG="$HOME/.claude/scripts/audit-log.sh"
|
|
23
|
-
ROTATE="$HOME/.claude/scripts/audit-log-rotate.sh"
|
|
24
|
-
|
|
25
|
-
if [ ! -x "$LOG" ]; then
|
|
26
|
-
fail "audit-log.sh missing or not executable: $LOG"
|
|
27
|
-
echo "══ aborted ══"; exit 2
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
echo "→ 1. Emit a sample PAT lookup"
|
|
31
|
-
TEST_IDENTITY="smoke-user"
|
|
32
|
-
AUDIT_FILE="$TMP_FILE" "$LOG" pat_lookup bitbucket "$TEST_IDENTITY" \
|
|
33
|
-
"https://bitbucket.example.com/proj/repo" true scope=REPO_WRITE
|
|
34
|
-
[ -s "$TMP_FILE" ] && pass "audit.jsonl has content after emit" \
|
|
35
|
-
|| fail "audit.jsonl empty after emit"
|
|
36
|
-
|
|
37
|
-
LINE=$(tail -1 "$TMP_FILE")
|
|
38
|
-
echo " emitted: $LINE"
|
|
39
|
-
|
|
40
|
-
echo ""
|
|
41
|
-
echo "→ 2. Repo URL is SHA-256 hashed (not plaintext)"
|
|
42
|
-
echo "$LINE" | grep -q '"repo_url_sha":"[a-f0-9]\{64\}"' \
|
|
43
|
-
&& pass "repo_url_sha is a 64-char hex SHA-256" \
|
|
44
|
-
|| fail "repo_url_sha not in expected SHA-256 form"
|
|
45
|
-
echo "$LINE" | grep -q "bitbucket.example.com" \
|
|
46
|
-
&& fail "PLAINTEXT URL leaked into audit log!" \
|
|
47
|
-
|| pass "Plaintext URL absent (only hash present)"
|
|
48
|
-
|
|
49
|
-
echo ""
|
|
50
|
-
echo "→ 3. Identity is logged by name (per section18.2.3 - not anonymized)"
|
|
51
|
-
echo "$LINE" | grep -q "\"identity\":\"$TEST_IDENTITY\"" \
|
|
52
|
-
&& pass "identity logged as plaintext name" \
|
|
53
|
-
|| fail "identity not logged or anonymized"
|
|
54
|
-
|
|
55
|
-
echo ""
|
|
56
|
-
echo "→ 4. Service is captured"
|
|
57
|
-
echo "$LINE" | grep -q '"service":"bitbucket"' \
|
|
58
|
-
&& pass "service field captured" \
|
|
59
|
-
|| fail "service field missing"
|
|
60
|
-
|
|
61
|
-
echo ""
|
|
62
|
-
echo "→ 5. Success flag is a JSON bool (not string)"
|
|
63
|
-
echo "$LINE" | grep -q '"success":true' \
|
|
64
|
-
&& pass "success is JSON bool" \
|
|
65
|
-
|| fail "success is not bool"
|
|
66
|
-
|
|
67
|
-
echo ""
|
|
68
|
-
echo "→ 6. Extras (key=value pairs) preserved"
|
|
69
|
-
echo "$LINE" | grep -q '"scope":"REPO_WRITE"' \
|
|
70
|
-
&& pass "extras preserved (scope=REPO_WRITE)" \
|
|
71
|
-
|| fail "extras not preserved"
|
|
72
|
-
|
|
73
|
-
echo ""
|
|
74
|
-
echo "→ 7. Rotation script exists + executable"
|
|
75
|
-
[ -x "$ROTATE" ] \
|
|
76
|
-
&& pass "audit-log-rotate.sh present + executable" \
|
|
77
|
-
|| fail "audit-log-rotate.sh missing or not executable"
|
|
78
|
-
|
|
79
|
-
echo ""
|
|
80
|
-
echo "→ 8. Rotation produces a dated archive + truncates live file"
|
|
81
|
-
# Drive a dry rotation in our TMP dir
|
|
82
|
-
AUDIT_DIR="$TMP_AUDIT" bash "$ROTATE" >/dev/null 2>&1
|
|
83
|
-
ROTATED=$(find "$TMP_AUDIT" -name 'audit.*.jsonl*' -type f 2>/dev/null | head -1)
|
|
84
|
-
[ -n "$ROTATED" ] \
|
|
85
|
-
&& pass "rotation produced archive: $(basename "$ROTATED")" \
|
|
86
|
-
|| fail "rotation produced no archive"
|
|
87
|
-
[ -f "$TMP_FILE" ] && [ ! -s "$TMP_FILE" ] \
|
|
88
|
-
&& pass "live audit.jsonl truncated to empty after rotation" \
|
|
89
|
-
|| fail "live audit.jsonl not properly truncated"
|
|
90
|
-
|
|
91
|
-
echo ""
|
|
92
|
-
echo "→ 9. .gitignore covers audit logs (sensitive - never sync upstream)"
|
|
93
|
-
SMOKE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
94
|
-
GITIGNORES=(
|
|
95
|
-
"$HOME/.claude/.gitignore"
|
|
96
|
-
"$SMOKE_DIR/../../.gitignore"
|
|
97
|
-
"$HOME/multi-agent-pipeline/.gitignore"
|
|
98
|
-
)
|
|
99
|
-
GITIGNORE_OK=false
|
|
100
|
-
for gi in "${GITIGNORES[@]}"; do
|
|
101
|
-
[ -f "$gi" ] && grep -qE 'audit\.jsonl|logs/multi-agent' "$gi" 2>/dev/null && GITIGNORE_OK=true
|
|
102
|
-
done
|
|
103
|
-
$GITIGNORE_OK \
|
|
104
|
-
&& pass ".gitignore covers audit.jsonl / logs/multi-agent" \
|
|
105
|
-
|| fail ".gitignore does NOT cover audit logs (sensitive!)"
|
|
106
|
-
|
|
107
|
-
echo ""
|
|
108
|
-
echo "→ 10. Quote-bearing fields are JSON-escaped (no malformed lines)"
|
|
109
|
-
REPO_LOG="$(cd "$(dirname "$0")" && pwd)/audit-log.sh"
|
|
110
|
-
ESC_FILE="$TMP_AUDIT/escape.jsonl"
|
|
111
|
-
AUDIT_FILE="$ESC_FILE" bash "$REPO_LOG" \
|
|
112
|
-
'pat_"lookup' 'bit"bucket' 'evil" ,"injected":"x' \
|
|
113
|
-
"https://x.example/r" true 'rea"son=quote'
|
|
114
|
-
ESC_LINE=$(tail -1 "$ESC_FILE")
|
|
115
|
-
if printf '%s' "$ESC_LINE" | python3 -c 'import json,sys; json.loads(sys.stdin.read())' 2>/dev/null; then
|
|
116
|
-
pass "line with embedded double-quotes parses as valid JSON"
|
|
117
|
-
else
|
|
118
|
-
fail "embedded quotes produced malformed JSON: $ESC_LINE"
|
|
119
|
-
fi
|
|
120
|
-
if printf '%s' "$ESC_LINE" | python3 -c 'import json,sys; d=json.load(sys.stdin); sys.exit(0 if "injected" not in d else 1)' 2>/dev/null; then
|
|
121
|
-
pass "quote in field value did not inject a sibling JSON key"
|
|
122
|
-
else
|
|
123
|
-
fail "quote in field value injected an extra top-level key"
|
|
124
|
-
fi
|
|
125
|
-
|
|
126
|
-
echo ""
|
|
127
|
-
echo "══ pat-audit smoke: $PASS passed, $FAIL failed ══"
|
|
128
|
-
[ "$FAIL" -eq 0 ]
|