@moretea-labs/forge 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +26 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +125 -101
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +69 -29
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +150 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +815 -1015
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +3 -2
- package/src/runtime/root/package-runtime-service.ts +48 -3
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- package/src/runtime/workflow/schedules/store.ts +79 -10
|
@@ -1,304 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
cat <<'USAGE_EOF'
|
|
6
|
-
Usage: scripts/check-architecture-sync.sh [--mode off|advisory|strict] [--target <branch>] [--changed-files <file>] [--format text|json]
|
|
7
|
-
|
|
8
|
-
Checks architecture request index integrity, then gates pending architecture
|
|
9
|
-
drift only for capabilities touched by the current branch or working tree.
|
|
10
|
-
USAGE_EOF
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
repo="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
14
|
-
repo="$(cd "$repo" && pwd)"
|
|
15
|
-
cd "$repo"
|
|
16
|
-
|
|
17
|
-
mode=""
|
|
18
|
-
target_branch=""
|
|
19
|
-
changed_files_file=""
|
|
20
|
-
format="text"
|
|
21
|
-
|
|
22
|
-
while [[ $# -gt 0 ]]; do
|
|
23
|
-
case "$1" in
|
|
24
|
-
--mode)
|
|
25
|
-
[[ -n "${2:-}" ]] || { echo "check-architecture-sync: --mode requires a value" >&2; exit 2; }
|
|
26
|
-
mode="$2"
|
|
27
|
-
shift 2
|
|
28
|
-
;;
|
|
29
|
-
--target)
|
|
30
|
-
[[ -n "${2:-}" ]] || { echo "check-architecture-sync: --target requires a value" >&2; exit 2; }
|
|
31
|
-
target_branch="$2"
|
|
32
|
-
shift 2
|
|
33
|
-
;;
|
|
34
|
-
--changed-files)
|
|
35
|
-
[[ -n "${2:-}" ]] || { echo "check-architecture-sync: --changed-files requires a value" >&2; exit 2; }
|
|
36
|
-
changed_files_file="$2"
|
|
37
|
-
shift 2
|
|
38
|
-
;;
|
|
39
|
-
--format)
|
|
40
|
-
[[ -n "${2:-}" ]] || { echo "check-architecture-sync: --format requires a value" >&2; exit 2; }
|
|
41
|
-
format="$2"
|
|
42
|
-
shift 2
|
|
43
|
-
;;
|
|
44
|
-
--help|-h)
|
|
45
|
-
usage
|
|
46
|
-
exit 0
|
|
47
|
-
;;
|
|
48
|
-
*)
|
|
49
|
-
echo "check-architecture-sync: unknown argument: $1" >&2
|
|
50
|
-
usage >&2
|
|
51
|
-
exit 2
|
|
52
|
-
;;
|
|
53
|
-
esac
|
|
54
|
-
done
|
|
55
|
-
|
|
56
|
-
json_escape() {
|
|
57
|
-
local value="$1"
|
|
58
|
-
value="${value//\\/\\\\}"
|
|
59
|
-
value="${value//\"/\\\"}"
|
|
60
|
-
value="${value//$'\n'/\\n}"
|
|
61
|
-
value="${value//$'\r'/\\r}"
|
|
62
|
-
value="${value//$'\t'/\\t}"
|
|
63
|
-
printf '%s' "$value"
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
policy_value() {
|
|
67
|
-
local jq_path="$1"
|
|
68
|
-
local default_value="$2"
|
|
69
|
-
local value=""
|
|
70
|
-
|
|
71
|
-
if [[ -f ".ai/harness/policy.json" ]] && command -v jq >/dev/null 2>&1; then
|
|
72
|
-
value="$(jq -r "$jq_path // empty" .ai/harness/policy.json 2>/dev/null || true)"
|
|
73
|
-
elif [[ -f ".ai/harness/policy.json" ]] && command -v node >/dev/null 2>&1; then
|
|
74
|
-
value="$(POLICY_PATH="$jq_path" node -e '
|
|
75
|
-
const fs = require("fs");
|
|
76
|
-
const path = process.env.POLICY_PATH || "";
|
|
77
|
-
try {
|
|
78
|
-
const policy = JSON.parse(fs.readFileSync(".ai/harness/policy.json", "utf8"));
|
|
79
|
-
const keys = path.replace(/^\./, "").split(".");
|
|
80
|
-
let value = policy;
|
|
81
|
-
for (const key of keys) value = value && value[key];
|
|
82
|
-
if (value === undefined || value === null || value === "") process.exit(1);
|
|
83
|
-
process.stdout.write(String(value));
|
|
84
|
-
} catch {
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
' 2>/dev/null || true)"
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
printf '%s' "${value:-$default_value}"
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
severity_rank() {
|
|
94
|
-
case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in
|
|
95
|
-
critical) printf '4' ;;
|
|
96
|
-
high) printf '3' ;;
|
|
97
|
-
medium) printf '2' ;;
|
|
98
|
-
low) printf '1' ;;
|
|
99
|
-
*) printf '0' ;;
|
|
100
|
-
esac
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
metadata_value() {
|
|
104
|
-
local file="$1"
|
|
105
|
-
local label="$2"
|
|
106
|
-
[[ -f "$file" ]] || return 1
|
|
107
|
-
awk -v label="> **${label}**:" '
|
|
108
|
-
index($0, label) == 1 {
|
|
109
|
-
value = substr($0, length(label) + 1)
|
|
110
|
-
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
|
111
|
-
gsub(/^`|`$/, "", value)
|
|
112
|
-
print value
|
|
113
|
-
exit
|
|
114
|
-
}
|
|
115
|
-
' "$file"
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
normalize_repo_path() {
|
|
119
|
-
local value="$1"
|
|
120
|
-
value="${value#file://}"
|
|
121
|
-
case "$value" in
|
|
122
|
-
"$repo"/*) value="${value#$repo/}" ;;
|
|
123
|
-
/*) return 1 ;;
|
|
124
|
-
./*) value="${value#./}" ;;
|
|
125
|
-
esac
|
|
126
|
-
value="${value%/}"
|
|
127
|
-
case "$value" in
|
|
128
|
-
""|.|..|../*|*/../*|*$'\n'*|*$'\r'*) return 1 ;;
|
|
129
|
-
esac
|
|
130
|
-
printf '%s\n' "$value"
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
collect_changed_files() {
|
|
134
|
-
local merge_base=""
|
|
135
|
-
|
|
136
|
-
if [[ -n "$changed_files_file" ]]; then
|
|
137
|
-
while IFS= read -r path; do
|
|
138
|
-
[[ -n "$path" ]] || continue
|
|
139
|
-
normalize_repo_path "$path" || true
|
|
140
|
-
done < "$changed_files_file"
|
|
141
|
-
return 0
|
|
142
|
-
fi
|
|
143
|
-
|
|
144
|
-
if [[ -n "$target_branch" ]] && git rev-parse --verify --quiet "$target_branch" >/dev/null 2>&1; then
|
|
145
|
-
merge_base="$(git merge-base HEAD "$target_branch" 2>/dev/null || true)"
|
|
146
|
-
fi
|
|
147
|
-
|
|
148
|
-
if [[ -n "$merge_base" ]]; then
|
|
149
|
-
git diff --name-only --diff-filter=ACMRTUXB "$merge_base"...HEAD
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
git status --porcelain=v1 --untracked-files=all | while IFS= read -r line; do
|
|
153
|
-
path="${line:3}"
|
|
154
|
-
case "$line" in
|
|
155
|
-
R*|C*)
|
|
156
|
-
path="${path##* -> }"
|
|
157
|
-
;;
|
|
158
|
-
esac
|
|
159
|
-
[[ -n "$path" ]] || continue
|
|
160
|
-
normalize_repo_path "$path" || true
|
|
161
|
-
done
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
pending_request_files() {
|
|
165
|
-
[[ -d "docs/architecture/requests" ]] || return 0
|
|
166
|
-
find "docs/architecture/requests" -maxdepth 1 -type f -name '*.md' | sort | while IFS= read -r request; do
|
|
167
|
-
[[ "$(metadata_value "$request" "Status" || true)" == "Pending" ]] || continue
|
|
168
|
-
printf '%s\n' "$request"
|
|
169
|
-
done
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
pending_requests_for_capabilities() {
|
|
173
|
-
local threshold="$1"
|
|
174
|
-
local min_rank capability_set="$2"
|
|
175
|
-
local request capability severity
|
|
176
|
-
min_rank="$(severity_rank "$threshold")"
|
|
177
|
-
|
|
178
|
-
while IFS= read -r request; do
|
|
179
|
-
capability="$(metadata_value "$request" "Capability ID" || true)"
|
|
180
|
-
capability="${capability:-root}"
|
|
181
|
-
severity="$(metadata_value "$request" "Severity" || true)"
|
|
182
|
-
[[ "$(severity_rank "$severity")" -ge "$min_rank" ]] || continue
|
|
183
|
-
if printf '%s\n' "$capability_set" | grep -Fxq "$capability"; then
|
|
184
|
-
printf '%s\t%s\t%s\n' "$capability" "$severity" "$request"
|
|
185
|
-
fi
|
|
186
|
-
done < <(pending_request_files)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
mode="${mode:-$(policy_value '.architecture.freshness_gate' 'advisory')}"
|
|
190
|
-
target_branch="${target_branch:-$(policy_value '.worktree_strategy.merge_back.target' 'main')}"
|
|
191
|
-
threshold="$(policy_value '.architecture.gate_min_severity' 'medium')"
|
|
192
|
-
|
|
193
|
-
case "$mode" in
|
|
194
|
-
off|advisory|strict) ;;
|
|
195
|
-
*)
|
|
196
|
-
echo "check-architecture-sync: unknown mode: $mode" >&2
|
|
197
|
-
exit 1
|
|
198
|
-
;;
|
|
199
|
-
esac
|
|
200
|
-
|
|
201
|
-
if [[ ! -x "scripts/architecture-queue.sh" ]]; then
|
|
202
|
-
if [[ "$mode" == "strict" ]]; then
|
|
203
|
-
echo "[ArchitectureSync] strict gate failed: missing scripts/architecture-queue.sh" >&2
|
|
204
|
-
exit 1
|
|
205
|
-
fi
|
|
206
|
-
echo "[ArchitectureSync] WARN: missing scripts/architecture-queue.sh; skipping advisory freshness gate" >&2
|
|
207
|
-
exit 0
|
|
208
|
-
fi
|
|
209
|
-
|
|
210
|
-
if ! bash scripts/architecture-queue.sh reindex --check >/dev/null; then
|
|
211
|
-
echo "[ArchitectureSync] architecture request index is stale; run bash scripts/architecture-queue.sh reindex" >&2
|
|
212
|
-
exit 1
|
|
213
|
-
fi
|
|
214
|
-
|
|
215
|
-
if [[ -f "docs/architecture/current/README.md" ]]; then
|
|
216
|
-
if [[ ! -f "docs/architecture/CURRENT.md" ]]; then
|
|
217
|
-
echo "[ArchitectureSync] architecture baseline failed: missing required file docs/architecture/CURRENT.md" >&2
|
|
218
|
-
exit 1
|
|
219
|
-
fi
|
|
220
|
-
|
|
221
|
-
if ! grep -Fq "Runtime Authority" "docs/architecture/CURRENT.md"; then
|
|
222
|
-
echo "[ArchitectureSync] architecture baseline failed: docs/architecture/CURRENT.md must contain: Runtime Authority" >&2
|
|
223
|
-
exit 1
|
|
224
|
-
fi
|
|
225
|
-
|
|
226
|
-
if ! grep -Fq "Not Runtime Authority" "docs/architecture/current/README.md"; then
|
|
227
|
-
echo "[ArchitectureSync] architecture baseline failed: docs/architecture/current/README.md must contain: Not Runtime Authority" >&2
|
|
228
|
-
exit 1
|
|
229
|
-
fi
|
|
230
|
-
fi
|
|
231
|
-
|
|
232
|
-
if [[ "$mode" == "off" ]]; then
|
|
233
|
-
case "$format" in
|
|
234
|
-
json) printf '{"mode":"off","changed_capabilities":0,"blocking":0}\n' ;;
|
|
235
|
-
text) echo "[ArchitectureSync] mode=off changed_capabilities=0 blocking=0" ;;
|
|
236
|
-
*) echo "check-architecture-sync: unsupported --format: $format" >&2; exit 2 ;;
|
|
237
|
-
esac
|
|
238
|
-
exit 0
|
|
239
|
-
fi
|
|
240
|
-
|
|
241
|
-
if ! command -v bun >/dev/null 2>&1 || [[ ! -f "scripts/capability-resolver.ts" ]]; then
|
|
242
|
-
if [[ "$mode" == "strict" ]]; then
|
|
243
|
-
echo "[ArchitectureSync] strict gate failed: missing bun or scripts/capability-resolver.ts" >&2
|
|
244
|
-
exit 1
|
|
245
|
-
fi
|
|
246
|
-
echo "[ArchitectureSync] WARN: missing bun or scripts/capability-resolver.ts; skipping advisory freshness gate" >&2
|
|
247
|
-
exit 0
|
|
248
|
-
fi
|
|
249
|
-
|
|
250
|
-
changed_files="$(collect_changed_files | sort -u)"
|
|
251
|
-
if [[ -z "$changed_files" ]]; then
|
|
252
|
-
case "$format" in
|
|
253
|
-
json) printf '{"mode":"%s","gate_min_severity":"%s","changed_capabilities":0,"blocking":0}\n' "$(json_escape "$mode")" "$(json_escape "$threshold")" ;;
|
|
254
|
-
text) echo "[ArchitectureSync] mode=$mode gate_min_severity=$threshold changed_capabilities=0 blocking=0" ;;
|
|
255
|
-
*) echo "check-architecture-sync: unsupported --format: $format" >&2; exit 2 ;;
|
|
256
|
-
esac
|
|
257
|
-
exit 0
|
|
258
|
-
fi
|
|
259
|
-
|
|
260
|
-
matches_json="$(printf '%s\n' "$changed_files" | bun scripts/capability-resolver.ts match --paths-from - --format json)"
|
|
261
|
-
capabilities="$(
|
|
262
|
-
MATCHES_JSON="$matches_json" node -e '
|
|
263
|
-
const matches = JSON.parse(process.env.MATCHES_JSON || "[]");
|
|
264
|
-
const ids = new Set();
|
|
265
|
-
for (const item of matches) ids.add(item.capability_id || "root");
|
|
266
|
-
for (const id of [...ids].sort()) console.log(id);
|
|
267
|
-
'
|
|
268
|
-
)"
|
|
269
|
-
changed_count="$(printf '%s\n' "$capabilities" | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
270
|
-
blocking_lines="$(pending_requests_for_capabilities "$threshold" "$capabilities")"
|
|
271
|
-
blocking_count="$(printf '%s\n' "$blocking_lines" | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
272
|
-
|
|
273
|
-
case "$format" in
|
|
274
|
-
json)
|
|
275
|
-
blocking_json="$(
|
|
276
|
-
printf '%s\n' "$blocking_lines" | awk -F '\t' 'NF >= 3 { printf "%s{\"capability_id\":\"%s\",\"severity\":\"%s\",\"request\":\"%s\"}", sep, $1, $2, $3; sep="," }'
|
|
277
|
-
)"
|
|
278
|
-
printf '{"mode":"%s","gate_min_severity":"%s","changed_capabilities":%s,"blocking":%s,"blocking_requests":[%s]}\n' \
|
|
279
|
-
"$(json_escape "$mode")" "$(json_escape "$threshold")" "$changed_count" "$blocking_count" "$blocking_json"
|
|
280
|
-
;;
|
|
281
|
-
text)
|
|
282
|
-
echo "[ArchitectureSync] mode=$mode gate_min_severity=$threshold changed_capabilities=$changed_count blocking=$blocking_count"
|
|
283
|
-
if [[ "$blocking_count" -gt 0 ]]; then
|
|
284
|
-
printf '%s\n' "$blocking_lines" | while IFS=$'\t' read -r capability severity request; do
|
|
285
|
-
[[ -n "$capability" ]] || continue
|
|
286
|
-
echo "[ArchitectureSync] pending $severity $capability -> $request"
|
|
287
|
-
done
|
|
288
|
-
fi
|
|
289
|
-
;;
|
|
290
|
-
*)
|
|
291
|
-
echo "check-architecture-sync: unsupported --format: $format" >&2
|
|
292
|
-
exit 2
|
|
293
|
-
;;
|
|
294
|
-
esac
|
|
295
|
-
|
|
296
|
-
if [[ "$blocking_count" -gt 0 ]]; then
|
|
297
|
-
if [[ "$mode" == "strict" ]]; then
|
|
298
|
-
echo "[ArchitectureSync] strict gate failed: changed capabilities have pending architecture requests" >&2
|
|
299
|
-
exit 1
|
|
300
|
-
fi
|
|
301
|
-
echo "[ArchitectureSync] WARN: changed capabilities have pending architecture requests" >&2
|
|
302
|
-
fi
|
|
303
|
-
|
|
304
|
-
exit 0
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
+
exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "check-architecture-sync.sh" "$@"
|
|
@@ -1,255 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
cat <<'USAGE_EOF'
|
|
6
|
-
Usage: scripts/check-brain-manifest.sh [--manifest PATH] [--require-vault]
|
|
7
|
-
|
|
8
|
-
Validates the repo-local external knowledge manifest and, when the configured
|
|
9
|
-
iCloud brain vault is available locally, checks that referenced vault files exist.
|
|
10
|
-
USAGE_EOF
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
manifest_path=".ai/harness/brain-manifest.json"
|
|
14
|
-
require_vault=0
|
|
15
|
-
|
|
16
|
-
while [[ $# -gt 0 ]]; do
|
|
17
|
-
case "$1" in
|
|
18
|
-
--manifest)
|
|
19
|
-
manifest_path="${2:-}"
|
|
20
|
-
shift 2
|
|
21
|
-
;;
|
|
22
|
-
--require-vault)
|
|
23
|
-
require_vault=1
|
|
24
|
-
shift
|
|
25
|
-
;;
|
|
26
|
-
--help|-h)
|
|
27
|
-
usage
|
|
28
|
-
exit 0
|
|
29
|
-
;;
|
|
30
|
-
*)
|
|
31
|
-
echo "Unknown argument: $1" >&2
|
|
32
|
-
usage
|
|
33
|
-
exit 1
|
|
34
|
-
;;
|
|
35
|
-
esac
|
|
36
|
-
done
|
|
37
|
-
|
|
38
|
-
resolve_js_runtime() {
|
|
39
|
-
if command -v node >/dev/null 2>&1; then
|
|
40
|
-
printf 'node'
|
|
41
|
-
return 0
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
if command -v bun >/dev/null 2>&1; then
|
|
45
|
-
printf 'bun'
|
|
46
|
-
return 0
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
return 1
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
runtime="$(resolve_js_runtime || true)"
|
|
53
|
-
if [[ -z "$runtime" ]]; then
|
|
54
|
-
echo "[brain] Missing node or bun to read brain manifest: $manifest_path"
|
|
55
|
-
exit 1
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
"$runtime" - "$manifest_path" "$require_vault" <<'JS_EOF'
|
|
59
|
-
const fs = require("fs");
|
|
60
|
-
const path = require("path");
|
|
61
|
-
const os = require("os");
|
|
62
|
-
|
|
63
|
-
const [, , manifestArg, requireVaultArg] = process.argv;
|
|
64
|
-
const requireVault = requireVaultArg === "1";
|
|
65
|
-
const repoRoot = process.cwd();
|
|
66
|
-
const manifestPath = path.resolve(repoRoot, manifestArg || ".ai/harness/brain-manifest.json");
|
|
67
|
-
const policyPath = path.resolve(repoRoot, ".ai/harness/policy.json");
|
|
68
|
-
let issues = 0;
|
|
69
|
-
|
|
70
|
-
function issue(message) {
|
|
71
|
-
console.log(`[brain] ${message}`);
|
|
72
|
-
issues += 1;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function warn(message) {
|
|
76
|
-
console.log(`[brain] warning: ${message}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function readJson(filePath, label) {
|
|
80
|
-
try {
|
|
81
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
82
|
-
} catch (error) {
|
|
83
|
-
issue(`Cannot read ${label}: ${path.relative(repoRoot, filePath) || filePath}`);
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function stripWildcard(logicalPath) {
|
|
89
|
-
return String(logicalPath || "").replace(/\/\*$/, "/");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const defaultBrainRoot = process.env.FORGE_BRAIN_ROOT ||
|
|
93
|
-
path.join(os.homedir(), "Library", "Mobile Documents", "com~apple~CloudDocs", "brain");
|
|
94
|
-
|
|
95
|
-
function logicalToLocal(logicalPath) {
|
|
96
|
-
const value = String(logicalPath || "");
|
|
97
|
-
if (value.startsWith("brain/")) {
|
|
98
|
-
return path.join(defaultBrainRoot, value.slice("brain/".length));
|
|
99
|
-
}
|
|
100
|
-
if (value.startsWith("~/")) {
|
|
101
|
-
return path.join(os.homedir(), value.slice(2));
|
|
102
|
-
}
|
|
103
|
-
return path.resolve(repoRoot, value);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function normalizeRel(filePath) {
|
|
107
|
-
return path.relative(repoRoot, path.resolve(repoRoot, filePath)).replaceAll(path.sep, "/");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function fileExistsWithSelfHostFallback(relPath) {
|
|
111
|
-
if (!relPath) return false;
|
|
112
|
-
if (fs.existsSync(path.resolve(repoRoot, relPath))) return true;
|
|
113
|
-
if (relPath.startsWith(".ai/harness/scripts/")) {
|
|
114
|
-
const helperName = path.basename(relPath);
|
|
115
|
-
return fs.existsSync(path.resolve(repoRoot, "scripts", helperName)) &&
|
|
116
|
-
fs.existsSync(path.resolve(repoRoot, "assets", "templates", "helpers", helperName));
|
|
117
|
-
}
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function hasDuplicate(values) {
|
|
122
|
-
return values.some((value, index) => value && values.indexOf(value) !== index);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!fs.existsSync(manifestPath)) {
|
|
126
|
-
issue(`Missing brain manifest: ${normalizeRel(manifestPath)}`);
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const manifest = readJson(manifestPath, "brain manifest");
|
|
131
|
-
const policy = fs.existsSync(policyPath) ? readJson(policyPath, "harness policy") : null;
|
|
132
|
-
|
|
133
|
-
if (!manifest) {
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const manifestRel = normalizeRel(manifestPath);
|
|
138
|
-
const externalKnowledge = policy?.information_lifecycle?.external_knowledge || {};
|
|
139
|
-
const policyManifest = externalKnowledge.manifest_file;
|
|
140
|
-
const policyProjectPath = externalKnowledge.project_path;
|
|
141
|
-
const policySyncScript = externalKnowledge.sync_script;
|
|
142
|
-
|
|
143
|
-
if (policyManifest && policyManifest !== manifestRel) {
|
|
144
|
-
issue(`Policy external_knowledge.manifest_file points to ${policyManifest}, expected ${manifestRel}`);
|
|
145
|
-
}
|
|
146
|
-
if (policySyncScript && !fileExistsWithSelfHostFallback(policySyncScript)) {
|
|
147
|
-
issue(`Policy external_knowledge.sync_script is missing: ${policySyncScript}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!manifest.version) {
|
|
151
|
-
issue("Brain manifest is missing version");
|
|
152
|
-
}
|
|
153
|
-
if (!manifest.project) {
|
|
154
|
-
issue("Brain manifest is missing project");
|
|
155
|
-
}
|
|
156
|
-
if (!manifest.default_brain_path) {
|
|
157
|
-
issue("Brain manifest is missing default_brain_path");
|
|
158
|
-
}
|
|
159
|
-
if (policyProjectPath && manifest.default_brain_path && policyProjectPath !== manifest.default_brain_path) {
|
|
160
|
-
issue(`Policy project_path ${policyProjectPath} does not match manifest default_brain_path ${manifest.default_brain_path}`);
|
|
161
|
-
}
|
|
162
|
-
if (!Array.isArray(manifest.entries)) {
|
|
163
|
-
issue("Brain manifest entries must be an array");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const entries = Array.isArray(manifest.entries) ? manifest.entries : [];
|
|
167
|
-
const ids = entries.map((entry) => entry.id);
|
|
168
|
-
const repoPaths = entries.map((entry) => entry.repo_path);
|
|
169
|
-
const brainPaths = entries.map((entry) => entry.brain_path);
|
|
170
|
-
const gbrainSlugs = entries.map((entry) => entry.gbrain_slug);
|
|
171
|
-
|
|
172
|
-
if (hasDuplicate(ids)) issue("Brain manifest contains duplicate entry ids");
|
|
173
|
-
if (hasDuplicate(repoPaths)) issue("Brain manifest contains duplicate repo_path values");
|
|
174
|
-
if (hasDuplicate(brainPaths)) issue("Brain manifest contains duplicate brain_path values");
|
|
175
|
-
if (hasDuplicate(gbrainSlugs)) issue("Brain manifest contains duplicate gbrain_slug values");
|
|
176
|
-
|
|
177
|
-
const defaultPrefix = stripWildcard(manifest.default_brain_path);
|
|
178
|
-
const localVaultRoot = logicalToLocal(defaultPrefix);
|
|
179
|
-
const shouldCheckVault = requireVault || fs.existsSync(localVaultRoot);
|
|
180
|
-
if (entries.length > 0 && !shouldCheckVault) {
|
|
181
|
-
warn(`vault root unavailable; skipped external file existence checks: ${localVaultRoot}`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
for (const entry of entries) {
|
|
185
|
-
const id = entry.id || "(missing id)";
|
|
186
|
-
const repoPath = entry.repo_path;
|
|
187
|
-
const assetPath = entry.asset_path;
|
|
188
|
-
const brainPath = entry.brain_path;
|
|
189
|
-
const gbrainSlug = entry.gbrain_slug;
|
|
190
|
-
const maxRepoLines = Number(entry.max_repo_lines || 0);
|
|
191
|
-
const syncDirection = entry.sync?.direction || entry.sync_direction || "";
|
|
192
|
-
const isRepoToBrainSync = syncDirection === "repo-to-brain";
|
|
193
|
-
|
|
194
|
-
if (!entry.id) issue("Entry is missing id");
|
|
195
|
-
if (!repoPath) issue(`Entry ${id} is missing repo_path`);
|
|
196
|
-
if (!brainPath) issue(`Entry ${id} is missing brain_path`);
|
|
197
|
-
if (!gbrainSlug) issue(`Entry ${id} is missing gbrain_slug`);
|
|
198
|
-
if (syncDirection && syncDirection !== "repo-to-brain") {
|
|
199
|
-
issue(`Entry ${id} has unsupported sync.direction: ${syncDirection}`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (brainPath && defaultPrefix && !String(brainPath).startsWith(defaultPrefix)) {
|
|
203
|
-
issue(`Entry ${id} brain_path is outside default_brain_path: ${brainPath}`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (repoPath) {
|
|
207
|
-
const repoFile = path.resolve(repoRoot, repoPath);
|
|
208
|
-
if (!fs.existsSync(repoFile)) {
|
|
209
|
-
issue(`Entry ${id} repo_path is missing: ${repoPath}`);
|
|
210
|
-
} else {
|
|
211
|
-
const content = fs.readFileSync(repoFile, "utf8");
|
|
212
|
-
if (!isRepoToBrainSync && brainPath && !content.includes(brainPath)) {
|
|
213
|
-
issue(`Entry ${id} repo stub does not mention brain_path: ${repoPath}`);
|
|
214
|
-
}
|
|
215
|
-
if (!isRepoToBrainSync && gbrainSlug && !content.includes(gbrainSlug)) {
|
|
216
|
-
issue(`Entry ${id} repo stub does not mention gbrain_slug: ${repoPath}`);
|
|
217
|
-
}
|
|
218
|
-
if (maxRepoLines > 0) {
|
|
219
|
-
const lineCount = content.endsWith("\n") ? content.split("\n").length - 1 : content.split("\n").length;
|
|
220
|
-
if (lineCount > maxRepoLines) {
|
|
221
|
-
issue(`Entry ${id} repo stub has ${lineCount} lines, max ${maxRepoLines}: ${repoPath}`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (repoPath && assetPath) {
|
|
228
|
-
const repoFile = path.resolve(repoRoot, repoPath);
|
|
229
|
-
const assetFile = path.resolve(repoRoot, assetPath);
|
|
230
|
-
if (!fs.existsSync(assetFile)) {
|
|
231
|
-
issue(`Entry ${id} asset_path is missing: ${assetPath}`);
|
|
232
|
-
} else if (fs.existsSync(repoFile)) {
|
|
233
|
-
const repoContent = fs.readFileSync(repoFile, "utf8");
|
|
234
|
-
const assetContent = fs.readFileSync(assetFile, "utf8");
|
|
235
|
-
if (repoContent !== assetContent) {
|
|
236
|
-
issue(`Entry ${id} repo_path and asset_path differ: ${repoPath} != ${assetPath}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (brainPath && shouldCheckVault) {
|
|
242
|
-
const localBrainFile = logicalToLocal(brainPath);
|
|
243
|
-
if (!fs.existsSync(localBrainFile)) {
|
|
244
|
-
issue(`Entry ${id} brain file is missing: ${brainPath}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (issues === 0) {
|
|
250
|
-
console.log("[brain] OK");
|
|
251
|
-
process.exit(0);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
process.exit(1);
|
|
255
|
-
JS_EOF
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
+
exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "check-brain-manifest.sh" "$@"
|
|
@@ -1,49 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
cat <<'USAGE_EOF'
|
|
6
|
-
Usage: scripts/check-context-files.sh
|
|
7
|
-
USAGE_EOF
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
11
|
-
usage
|
|
12
|
-
exit 0
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
issues=0
|
|
16
|
-
files="$(
|
|
17
|
-
find . \
|
|
18
|
-
\( -path './.git' -o -path './node_modules' -o -path './_ref' -o -path './_ops' -o -path './.worktrees' \) -prune -o \
|
|
19
|
-
-type f \( -name 'AGENTS.md' -o -name 'CLAUDE.md' \) -print \
|
|
20
|
-
| sort
|
|
21
|
-
)"
|
|
22
|
-
|
|
23
|
-
if [[ -z "$files" ]]; then
|
|
24
|
-
echo "[ContextScan] No AGENTS.md or CLAUDE.md files found."
|
|
25
|
-
exit 0
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
scan_pattern() {
|
|
29
|
-
local label="$1"
|
|
30
|
-
local regex="$2"
|
|
31
|
-
local match
|
|
32
|
-
|
|
33
|
-
match="$(printf '%s\n' "$files" | xargs grep -Eni "$regex" 2>/dev/null | head -1 || true)"
|
|
34
|
-
if [[ -n "$match" ]]; then
|
|
35
|
-
echo "[ContextScan] ${label}: ${match}"
|
|
36
|
-
issues=$((issues + 1))
|
|
37
|
-
fi
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
scan_pattern "prompt-injection" '(ignore (all|any|previous) instructions|reveal (the )?(system|developer) prompt|bypass (the )?(guard|policy)|override (the )?(policy|guard))'
|
|
41
|
-
scan_pattern "secret-exfiltration" '(print|show|copy|upload|send).*(api key|token|secret|credential|\.env)|curl .*https?://|wget .*https?://'
|
|
42
|
-
scan_pattern "filesystem-exfiltration" '(read|cat|copy).*(\.env|id_rsa|config\.yaml|secrets?)'
|
|
43
|
-
|
|
44
|
-
if [[ "$issues" -gt 0 ]]; then
|
|
45
|
-
echo "[ContextScan] FAIL (${issues} suspicious pattern(s))"
|
|
46
|
-
exit 1
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
echo "[ContextScan] SAFE"
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
+
exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "check-context-files.sh" "$@"
|