@brainwav/diagram 1.0.8 → 1.1.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.
Files changed (91) hide show
  1. package/.diagram/contracts/machine-command-coverage.json +73 -0
  2. package/.diagram/migration/finalization-policy.json +20 -0
  3. package/LICENSE +202 -21
  4. package/README.md +132 -339
  5. package/package.json +46 -13
  6. package/scripts/refresh-diagram-context.sh +274 -182
  7. package/src/analyzers/default-analyzer.js +11 -0
  8. package/src/analyzers/index.js +34 -0
  9. package/src/artifacts/agent-context.js +105 -0
  10. package/src/artifacts/artifact-budget.js +224 -0
  11. package/src/artifacts/brief.js +153 -0
  12. package/src/artifacts/evidence-manifest.js +206 -0
  13. package/src/artifacts/evidence-summary.js +29 -0
  14. package/src/commands/analyze.js +125 -0
  15. package/src/commands/changed.js +185 -0
  16. package/src/commands/context.js +110 -0
  17. package/src/commands/diff.js +142 -0
  18. package/src/commands/doctor.js +335 -0
  19. package/src/commands/explain.js +273 -0
  20. package/src/commands/generate-all.js +170 -0
  21. package/src/commands/generate-animated.js +50 -0
  22. package/src/commands/generate-video.js +65 -0
  23. package/src/commands/generate.js +522 -0
  24. package/src/commands/init.js +123 -0
  25. package/src/commands/output.js +76 -0
  26. package/src/commands/scan.js +624 -0
  27. package/src/commands/shared.js +396 -0
  28. package/src/commands/validate.js +328 -0
  29. package/src/commands/video-shared.js +105 -0
  30. package/src/commands/workflow-pr.js +26 -0
  31. package/src/confidence/pipeline.js +186 -0
  32. package/src/config/diagramrc.js +79 -0
  33. package/src/context/build-context-pack.js +291 -0
  34. package/src/context/normalize-diagram-manifest.js +282 -0
  35. package/src/core/analysis-generation-analyze-components.js +102 -0
  36. package/src/core/analysis-generation-analyze-dependencies.js +33 -0
  37. package/src/core/analysis-generation-analyze-files.js +48 -0
  38. package/src/core/analysis-generation-analyze-options.js +73 -0
  39. package/src/core/analysis-generation-analyze.js +63 -0
  40. package/src/core/analysis-generation-constants.js +53 -0
  41. package/src/core/analysis-generation-diagrams-core-architecture.js +105 -0
  42. package/src/core/analysis-generation-diagrams-core-dependency.js +68 -0
  43. package/src/core/analysis-generation-diagrams-core-sequence.js +142 -0
  44. package/src/core/analysis-generation-diagrams-core-shapes.js +104 -0
  45. package/src/core/analysis-generation-diagrams-core.js +12 -0
  46. package/src/core/analysis-generation-diagrams-empty.js +68 -0
  47. package/src/core/analysis-generation-diagrams-erd.js +59 -0
  48. package/src/core/analysis-generation-diagrams-limit.js +27 -0
  49. package/src/core/analysis-generation-diagrams-role-ai-agent.js +103 -0
  50. package/src/core/analysis-generation-diagrams-role-ai-context.js +186 -0
  51. package/src/core/analysis-generation-diagrams-role-ai.js +11 -0
  52. package/src/core/analysis-generation-diagrams-role-data.js +182 -0
  53. package/src/core/analysis-generation-diagrams-role-helpers.js +129 -0
  54. package/src/core/analysis-generation-diagrams-role-security.js +129 -0
  55. package/src/core/analysis-generation-diagrams-role.js +25 -0
  56. package/src/core/analysis-generation-diagrams.js +182 -0
  57. package/src/core/analysis-generation-role-tags-constants.js +55 -0
  58. package/src/core/analysis-generation-role-tags-imports.js +32 -0
  59. package/src/core/analysis-generation-role-tags-infer.js +49 -0
  60. package/src/core/analysis-generation-role-tags-match.js +19 -0
  61. package/src/core/analysis-generation-role-tags.js +7 -0
  62. package/src/core/analysis-generation-utils-core.js +308 -0
  63. package/src/core/analysis-generation-utils-graph.js +321 -0
  64. package/src/core/analysis-generation-utils-resolution.js +76 -0
  65. package/src/core/analysis-generation-utils.js +9 -0
  66. package/src/core/analysis-generation.js +44 -0
  67. package/src/diagram.js +178 -1761
  68. package/src/formatters/console.js +198 -0
  69. package/src/formatters/index.js +41 -0
  70. package/src/formatters/json.js +113 -0
  71. package/src/formatters/junit.js +123 -0
  72. package/src/graph.js +159 -0
  73. package/src/incremental/cache.js +210 -0
  74. package/src/ir/architecture-ir.js +48 -0
  75. package/src/migration/evidence.js +262 -0
  76. package/src/migration/finalization-policy.js +35 -0
  77. package/src/renderers/report-html.js +265 -0
  78. package/src/rules/factory.js +108 -0
  79. package/src/rules/types/base.js +54 -0
  80. package/src/rules/types/import-rule.js +286 -0
  81. package/src/rules.js +380 -0
  82. package/src/schema/erd-confidence.js +56 -0
  83. package/src/schema/erd-extractor.js +504 -0
  84. package/src/schema/erd-model.js +176 -0
  85. package/src/schema/rules-schema.js +170 -0
  86. package/src/utils/suggestions.js +67 -0
  87. package/src/video.js +4 -5
  88. package/src/workflow/git-helpers.js +576 -0
  89. package/src/workflow/pr-command.js +694 -0
  90. package/src/workflow/pr-impact.js +848 -0
  91. package/src/workflow/sort-utils.js +16 -0
@@ -1,199 +1,291 @@
1
1
  #!/usr/bin/env bash
2
- #
3
- # refresh-diagram-context.sh - Generate Mermaid diagrams and compact into AI context
4
- #
5
- # Usage:
6
- # scripts/refresh-diagram-context.sh --dry-run # Preview actions without changes
7
- # scripts/refresh-diagram-context.sh --force # Execute refresh
8
- #
9
- # Outputs:
10
- # .diagram/*.mmd - Individual Mermaid diagrams
11
- # .diagram/context/diagram-context.md - Compacted context for AI agents
12
- # .diagram/context/diagram-context.meta.json - Metadata with schema version
13
- #
14
2
  set -euo pipefail
15
3
 
16
- # Configuration
17
- REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
18
- DIAGRAM_ENTRYPOINT="$REPO_ROOT/src/diagram.js"
19
- DIAGRAMS_DIR="$REPO_ROOT/.diagram"
20
- CONTEXT_DIR="$REPO_ROOT/.diagram/context"
4
+ QUIET=0
5
+ FORCE=0
6
+ DRY_RUN=0
7
+ CHECK=0
8
+
9
+ while [[ $# -gt 0 ]]; do
10
+ case "$1" in
11
+ --quiet)
12
+ QUIET=1
13
+ shift
14
+ ;;
15
+ --force)
16
+ FORCE=1
17
+ shift
18
+ ;;
19
+ --dry-run)
20
+ DRY_RUN=1
21
+ shift
22
+ ;;
23
+ --check)
24
+ CHECK=1
25
+ shift
26
+ ;;
27
+ *)
28
+ echo "Unknown option: $1" >&2
29
+ exit 2
30
+ ;;
31
+ esac
32
+ done
33
+
34
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
35
+ bash "$ROOT_DIR/scripts/codex-preflight.sh" --mode optional
36
+
37
+ DIAGRAM_DIR="$ROOT_DIR/.diagram"
38
+ CONTEXT_DIR="$DIAGRAM_DIR/context"
21
39
  CONTEXT_FILE="$CONTEXT_DIR/diagram-context.md"
22
40
  META_FILE="$CONTEXT_DIR/diagram-context.meta.json"
23
- SCHEMA_VERSION="1.0"
24
-
25
- # State
26
- DRY_RUN=false
27
- FORCE=false
28
-
29
- usage() {
30
- cat <<'USAGE'
31
- Usage:
32
- scripts/refresh-diagram-context.sh --dry-run # Preview actions without changes
33
- scripts/refresh-diagram-context.sh --force # Execute refresh
34
-
35
- Options:
36
- --dry-run Show what would be done without making changes
37
- --force Execute the refresh (required for actual changes)
38
- -h, --help Show this help message
39
-
40
- Outputs:
41
- .diagram/*.mmd Individual Mermaid diagrams
42
- .diagram/context/diagram-context.md Compacted context for AI agents
43
- .diagram/context/diagram-context.meta.json Metadata with schema version
44
- USAGE
45
- }
46
-
47
- parse_args() {
48
- while [[ $# -gt 0 ]]; do
49
- case "$1" in
50
- --dry-run)
51
- DRY_RUN=true
52
- shift
53
- ;;
54
- --force)
55
- FORCE=true
56
- shift
57
- ;;
58
- -h|--help)
59
- usage
60
- exit 0
61
- ;;
62
- *)
63
- echo "Unknown option: $1" >&2
64
- usage >&2
65
- exit 2
66
- ;;
67
- esac
68
- done
69
- }
41
+ GIT_STATE_FILE="$(git -C "$ROOT_DIR" rev-parse --git-path archscope/refresh-state.local.json 2>/dev/null || true)"
42
+ if [[ -n "$GIT_STATE_FILE" && "$GIT_STATE_FILE" != /* ]]; then
43
+ GIT_STATE_FILE="$ROOT_DIR/$GIT_STATE_FILE"
44
+ fi
45
+ ROOT_STATE_HASH="$(printf '%s' "$ROOT_DIR" | shasum -a 256 | awk '{print $1}')"
46
+ FALLBACK_STATE_DIR="${DIAGRAM_REFRESH_STATE_DIR:-${TMPDIR:-/tmp}/archscope-refresh-state/$ROOT_STATE_HASH}"
47
+ STATE_FILE="${GIT_STATE_FILE:-$FALLBACK_STATE_DIR/refresh-state.local.json}"
48
+ LOG_FILE="$(dirname "$STATE_FILE")/refresh.log"
49
+ MIN_SECONDS="${DIAGRAM_REFRESH_MIN_SECONDS:-1800}"
50
+ MAX_FILES="${DIAGRAM_REFRESH_MAX_FILES:-1000}"
51
+ CONTEXT_MAX_BYTES="${DIAGRAM_CONTEXT_MAX_BYTES:-12000}"
52
+ CONTEXT_MAX_LINES_PER_DIAGRAM="${DIAGRAM_CONTEXT_MAX_LINES_PER_DIAGRAM:-140}"
53
+ CONTEXT_MAX_EMBEDDED_DIAGRAMS="${DIAGRAM_CONTEXT_MAX_EMBEDDED_DIAGRAMS:-3}"
54
+ NOW_EPOCH="$(date +%s)"
55
+
56
+ if ! mkdir -p "$(dirname "$STATE_FILE")" 2>/dev/null; then
57
+ STATE_FILE="$FALLBACK_STATE_DIR/refresh-state.local.json"
58
+ mkdir -p "$(dirname "$STATE_FILE")"
59
+ fi
60
+ LOG_FILE="$(dirname "$STATE_FILE")/refresh.log"
61
+ if [[ "$CHECK" -ne 1 && "$DRY_RUN" -ne 1 ]]; then
62
+ mkdir -p "$DIAGRAM_DIR" "$CONTEXT_DIR"
63
+ fi
70
64
 
71
65
  log() {
72
- echo "[refresh] $1"
73
- }
74
-
75
- log_dry() {
76
- echo "[dry-run] $1"
77
- }
78
-
79
- ensure_dirs() {
80
- if [[ "$DRY_RUN" == "true" ]]; then
81
- log_dry "Would use: $DIAGRAMS_DIR"
82
- log_dry "Would create: $CONTEXT_DIR"
83
- else
84
- mkdir -p "$DIAGRAMS_DIR"
85
- mkdir -p "$CONTEXT_DIR"
86
- fi
87
- }
88
-
89
- generate_diagrams() {
90
- if [[ "$DRY_RUN" == "true" ]]; then
91
- log_dry "Would generate diagrams to: $DIAGRAMS_DIR"
92
- log_dry " - All diagram types via 'diagram all'"
93
- return 0
94
- fi
95
-
96
- if [[ "$FORCE" != "true" ]]; then
97
- echo "Error: --force required to execute refresh" >&2
98
- exit 1
99
- fi
100
-
101
- log "Generating diagrams to: $DIAGRAMS_DIR"
102
-
103
- # Use 'diagram all' command with .diagram output directory
104
- node "$DIAGRAM_ENTRYPOINT" all "$REPO_ROOT" --output-dir "$DIAGRAMS_DIR"
66
+ local message="$1"
67
+ printf '[%s] %s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "$message" >> "$LOG_FILE"
68
+ if [[ "$QUIET" -ne 1 ]]; then
69
+ printf '%s\n' "$message"
70
+ fi
105
71
  }
106
72
 
107
- compact_context() {
108
- if [[ "$DRY_RUN" == "true" ]]; then
109
- log_dry "Would compact diagrams into: $CONTEXT_FILE"
110
- return 0
111
- fi
112
-
113
- log "Compacting diagrams into: $CONTEXT_FILE"
114
-
115
- # Build header
116
- cat > "$CONTEXT_FILE" <<'HEADER'
117
- # Diagram Context Pack
118
-
119
- Auto-generated architecture context from Mermaid diagrams.
120
- Do not edit manually - regenerate with `scripts/refresh-diagram-context.sh --force`.
121
-
122
- ---
123
-
124
- HEADER
125
-
126
- # Diagram types to include (matches diagram all output)
127
- local diagram_types=("architecture" "dependency" "class" "sequence" "flow" "database" "user" "events" "auth" "security")
128
-
129
- for dtype in "${diagram_types[@]}"; do
130
- local mmd_file="$DIAGRAMS_DIR/${dtype}.mmd"
131
- if [[ -f "$mmd_file" ]]; then
132
- echo "## ${dtype^} Diagram" >> "$CONTEXT_FILE"
133
- echo "" >> "$CONTEXT_FILE"
134
- echo '```mermaid' >> "$CONTEXT_FILE"
135
- cat "$mmd_file" >> "$CONTEXT_FILE"
136
- echo '```' >> "$CONTEXT_FILE"
137
- echo "" >> "$CONTEXT_FILE"
138
- echo "---" >> "$CONTEXT_FILE"
139
- echo "" >> "$CONTEXT_FILE"
140
- fi
141
- done
142
-
143
- log "Context file created: $CONTEXT_FILE"
73
+ if [[ "$DRY_RUN" -eq 1 ]]; then
74
+ log "dry-run: would refresh diagrams into $DIAGRAM_DIR and context at $CONTEXT_FILE"
75
+ exit 0
76
+ fi
77
+
78
+ COOLDOWN_FILE=""
79
+ if [[ -f "$STATE_FILE" ]]; then
80
+ COOLDOWN_FILE="$STATE_FILE"
81
+ elif [[ -f "$META_FILE" ]]; then
82
+ COOLDOWN_FILE="$META_FILE"
83
+ fi
84
+
85
+ if [[ "$CHECK" -ne 1 && "$FORCE" -ne 1 && -n "$COOLDOWN_FILE" ]]; then
86
+ last_epoch="$(jq -r '.last_generated_epoch // 0' "$COOLDOWN_FILE" 2>/dev/null || echo 0)"
87
+ if [[ "$last_epoch" =~ ^[0-9]+$ ]]; then
88
+ age=$((NOW_EPOCH - last_epoch))
89
+ if (( age < MIN_SECONDS )); then
90
+ log "skip: cooldown active (${age}s < ${MIN_SECONDS}s)"
91
+ exit 0
92
+ fi
93
+ fi
94
+ fi
95
+
96
+ if ! command -v node >/dev/null 2>&1; then
97
+ log "error: node runtime is not available"
98
+ exit 1
99
+ fi
100
+
101
+ TRUNC_DIR=".tmp-diagram-refresh-XXXXXX"
102
+ TMP_DIR="$(mktemp -d "$ROOT_DIR/${TRUNC_DIR}")"
103
+ TMP_BASENAME="$(basename "$TMP_DIR")"
104
+ EXCLUDE_PATTERNS="node_modules/**,.git/**,dist/**,${TMP_BASENAME}/**"
105
+ trap 'rm -rf "$TMP_DIR"' EXIT
106
+
107
+ pushd "$ROOT_DIR" >/dev/null
108
+ GENERATE_ALL_CMD=(
109
+ node src/diagram.js generate-all .
110
+ --output-dir "$TMP_BASENAME/diagrams"
111
+ --artifact-profile agent
112
+ --deterministic
113
+ --exclude "$EXCLUDE_PATTERNS"
114
+ --max-files "$MAX_FILES"
115
+ )
116
+ if [[ "$QUIET" -eq 1 ]]; then
117
+ GENERATE_ALL_CMD+=(--quiet)
118
+ fi
119
+ "${GENERATE_ALL_CMD[@]}"
120
+ popd >/dev/null
121
+
122
+ if ! ls "$TMP_DIR/diagrams"/*.mmd >/dev/null 2>&1; then
123
+ log "error: no .mmd files produced"
124
+ exit 1
125
+ fi
126
+
127
+ MANIFEST_PATH="$TMP_DIR/diagrams/manifest.json"
128
+ # shellcheck disable=SC2097,SC2098
129
+ ROOT_DIR="$ROOT_DIR" TMP_DIR="$TMP_DIR" MANIFEST_PATH="$MANIFEST_PATH" \
130
+ node "$ROOT_DIR/src/context/normalize-diagram-manifest.js"
131
+
132
+ TMP_CONTEXT="$TMP_DIR/diagram-context.md"
133
+ TMP_CONTEXT_META="$TMP_DIR/diagram-context.meta.json"
134
+ # shellcheck disable=SC2097,SC2098,SC2034
135
+ ROOT_DIR="$ROOT_DIR" \
136
+ TMP_DIR="$TMP_DIR" \
137
+ CONTEXT_MAX_BYTES="$CONTEXT_MAX_BYTES" \
138
+ CONTEXT_MAX_LINES_PER_DIAGRAM="$CONTEXT_MAX_LINES_PER_DIAGRAM" \
139
+ CONTEXT_MAX_EMBEDDED_DIAGRAMS="$CONTEXT_MAX_EMBEDDED_DIAGRAMS" \
140
+ CONTEXT_DETERMINISTIC=1 \
141
+ CONTEXT_OUTPUT_PATH="$TMP_CONTEXT" \
142
+ CONTEXT_META_OUTPUT_PATH="$TMP_CONTEXT_META" \
143
+ node "$ROOT_DIR/src/context/build-context-pack.js"
144
+
145
+ CONTEXT_SHA="$(shasum -a 256 "$TMP_CONTEXT" | awk '{print $1}')"
146
+ GIT_HEAD="$(git -C "$ROOT_DIR" rev-parse --short HEAD 2>/dev/null || echo "unknown")"
147
+ # shellcheck disable=SC2012
148
+ DIAGRAM_COUNT="$(ls "$TMP_DIR/diagrams"/*.mmd | wc -l | tr -d ' ')"
149
+ TMP_STABLE_META="$TMP_DIR/diagram-context.stable-meta.json"
150
+ TMP_STATE="$TMP_DIR/refresh-state.local.json"
151
+
152
+ if [[ ! -f "$TMP_CONTEXT_META" ]]; then
153
+ log "error: context pack metadata sidecar missing at $TMP_CONTEXT_META"
154
+ exit 1
155
+ fi
156
+
157
+ jq --tab \
158
+ --arg context_sha256 "$CONTEXT_SHA" \
159
+ --argjson diagram_count "$DIAGRAM_COUNT" \
160
+ --argjson context_max_bytes "$CONTEXT_MAX_BYTES" \
161
+ --argjson context_max_lines_per_diagram "$CONTEXT_MAX_LINES_PER_DIAGRAM" \
162
+ --argjson context_max_embedded_diagrams "$CONTEXT_MAX_EMBEDDED_DIAGRAMS" \
163
+ --argjson context_bytes "$(wc -c < "$TMP_CONTEXT" | tr -d ' ')" \
164
+ '
165
+ . as $packer
166
+ | ($packer // {})
167
+ | del(.rootPath)
168
+ | .schema_version = 1
169
+ | .generated_at = (.generatedAt // "1970-01-01T00:00:00.000Z")
170
+ | .context_sha256 = $context_sha256
171
+ | .context_bytes = $context_bytes
172
+ | .context_max_bytes = $context_max_bytes
173
+ | .context_max_lines_per_diagram = $context_max_lines_per_diagram
174
+ | .context_max_embedded_diagrams = $context_max_embedded_diagrams
175
+ | .diagram_count = $diagram_count
176
+ | .context_path = ".diagram/context/diagram-context.md"
177
+ | .context_meta_path = ".diagram/context/diagram-context.meta.json"
178
+ | .diagram_manifest_path = ".diagram/manifest.json"
179
+ | .embedded_diagram_count = ((.embeddedCount // 0) | tonumber)
180
+ | .omittedTypes = ((.omittedTypes // []) | sort)
181
+ | .omitted_types = .omittedTypes
182
+ | .omitted_count = (.omittedTypes | length)
183
+ ' "$TMP_CONTEXT_META" > "$TMP_STABLE_META"
184
+
185
+ CHANGED=false
186
+ changed_files=()
187
+
188
+ mark_changed() {
189
+ CHANGED=true
190
+ changed_files+=("$1")
144
191
  }
145
192
 
146
- write_metadata() {
147
- if [[ "$DRY_RUN" == "true" ]]; then
148
- log_dry "Would write metadata to: $META_FILE"
149
- return 0
150
- fi
151
-
152
- local timestamp
153
- timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
154
-
155
- local diagram_count
156
- diagram_count=$(find "$DIAGRAMS_DIR" -maxdepth 1 -name "*.mmd" -type f 2>/dev/null | wc -l | tr -d ' ')
157
-
158
- cat > "$META_FILE" <<EOF
159
- {
160
- "schema_version": "$SCHEMA_VERSION",
161
- "generated_at": "$timestamp",
162
- "repo_root": "$REPO_ROOT",
163
- "diagrams_dir": ".diagram",
164
- "context_file": ".diagram/context/diagram-context.md",
165
- "diagram_count": $diagram_count,
166
- "diagram_types": ["architecture", "dependency", "class", "sequence", "flow", "database", "user", "events", "auth", "security"]
193
+ compare_file() {
194
+ local source_file="$1"
195
+ local target_file="$2"
196
+ local label="$3"
197
+ if [[ ! -f "$target_file" ]] || ! cmp -s "$source_file" "$target_file"; then
198
+ mark_changed "$label"
199
+ fi
167
200
  }
168
- EOF
169
201
 
170
- log "Metadata file created: $META_FILE"
202
+ collect_mmd_names() {
203
+ local source_dir="$1"
204
+ local names=()
205
+ local file
206
+ for file in "$source_dir"/*.mmd; do
207
+ [[ -e "$file" ]] || continue
208
+ names+=("$(basename "$file")")
209
+ done
210
+ if [[ "${#names[@]}" -gt 0 ]]; then
211
+ printf '%s\n' "${names[@]}" | sort
212
+ fi
171
213
  }
172
214
 
173
- main() {
174
- parse_args "$@"
175
-
176
- echo "== Diagram Context Refresh =="
177
- echo "Repo: $REPO_ROOT"
178
- echo "Dry-run: $DRY_RUN"
179
- echo "Force: $FORCE"
180
- echo ""
181
-
182
- ensure_dirs
183
- generate_diagrams
184
- compact_context
185
- write_metadata
186
-
187
- if [[ "$DRY_RUN" == "true" ]]; then
188
- echo ""
189
- echo "Dry-run complete. Run with --force to execute."
190
- else
191
- echo ""
192
- echo "Refresh complete."
193
- echo " Diagrams: .diagram/"
194
- echo " Context: .diagram/context/diagram-context.md"
195
- echo " Metadata: .diagram/context/diagram-context.meta.json"
196
- fi
215
+ compare_file "$TMP_CONTEXT" "$CONTEXT_FILE" ".diagram/context/diagram-context.md"
216
+ compare_file "$TMP_DIR/diagrams/manifest.json" "$DIAGRAM_DIR/manifest.json" ".diagram/manifest.json"
217
+ compare_file "$TMP_STABLE_META" "$META_FILE" ".diagram/context/diagram-context.meta.json"
218
+
219
+ tmp_mmd_list="$(collect_mmd_names "$TMP_DIR/diagrams")"
220
+ current_mmd_list="$(collect_mmd_names "$DIAGRAM_DIR")"
221
+ if [[ "$tmp_mmd_list" != "$current_mmd_list" ]]; then
222
+ mark_changed ".diagram/*.mmd"
223
+ else
224
+ while IFS= read -r mmd_name; do
225
+ [[ -n "$mmd_name" ]] || continue
226
+ compare_file "$TMP_DIR/diagrams/$mmd_name" "$DIAGRAM_DIR/$mmd_name" ".diagram/$mmd_name"
227
+ done <<< "$tmp_mmd_list"
228
+ fi
229
+
230
+ if [[ "$CHECK" -eq 1 ]]; then
231
+ if [[ "$CHANGED" == "true" ]]; then
232
+ log "check: diagram context is stale"
233
+ {
234
+ printf 'diagram context is stale; refresh required for:\n'
235
+ printf ' %s\n' "${changed_files[@]}"
236
+ } >&2
237
+ exit 1
238
+ fi
239
+ log "check: diagram context is current"
240
+ exit 0
241
+ fi
242
+
243
+ copy_if_changed() {
244
+ local source_file="$1"
245
+ local target_file="$2"
246
+ if [[ ! -f "$target_file" ]] || ! cmp -s "$source_file" "$target_file"; then
247
+ cp "$source_file" "$target_file"
248
+ fi
197
249
  }
198
250
 
199
- main "$@"
251
+ while IFS= read -r mmd_name; do
252
+ [[ -n "$mmd_name" ]] || continue
253
+ copy_if_changed "$TMP_DIR/diagrams/$mmd_name" "$DIAGRAM_DIR/$mmd_name"
254
+ done <<< "$tmp_mmd_list"
255
+
256
+ while IFS= read -r mmd_name; do
257
+ [[ -n "$mmd_name" ]] || continue
258
+ if ! printf '%s\n' "$tmp_mmd_list" | grep -Fxq "$mmd_name"; then
259
+ rm -f "$DIAGRAM_DIR/$mmd_name"
260
+ fi
261
+ done <<< "$current_mmd_list"
262
+
263
+ copy_if_changed "$TMP_DIR/diagrams/manifest.json" "$DIAGRAM_DIR/manifest.json"
264
+ copy_if_changed "$TMP_CONTEXT" "$CONTEXT_FILE"
265
+ copy_if_changed "$TMP_STABLE_META" "$META_FILE"
266
+
267
+ jq --tab \
268
+ --arg generated_at "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
269
+ --arg git_head "$GIT_HEAD" \
270
+ --arg context_sha256 "$CONTEXT_SHA" \
271
+ --argjson context_bytes "$(wc -c < "$TMP_CONTEXT" | tr -d ' ')" \
272
+ --argjson last_generated_epoch "$NOW_EPOCH" \
273
+ --argjson min_interval_seconds "$MIN_SECONDS" \
274
+ --arg changed "$CHANGED" \
275
+ --arg root_path "$ROOT_DIR" \
276
+ '
277
+ {
278
+ schema_version: 1,
279
+ generated_at: $generated_at,
280
+ root_path: $root_path,
281
+ git_head: $git_head,
282
+ context_sha256: $context_sha256,
283
+ context_bytes: $context_bytes,
284
+ last_generated_epoch: $last_generated_epoch,
285
+ min_interval_seconds: $min_interval_seconds,
286
+ changed: ($changed == "true")
287
+ }
288
+ ' "$TMP_STABLE_META" > "$TMP_STATE"
289
+ copy_if_changed "$TMP_STATE" "$STATE_FILE"
290
+
291
+ log "ok: refreshed ${DIAGRAM_COUNT} diagrams (changed=${CHANGED})"
@@ -0,0 +1,11 @@
1
+ const { analyze } = require('../core/analysis-generation');
2
+
3
+ const defaultAnalyzer = {
4
+ name: 'default',
5
+ version: '1.0.0',
6
+ analyze(rootPath, options) {
7
+ return analyze(rootPath, options);
8
+ },
9
+ };
10
+
11
+ module.exports = { defaultAnalyzer };
@@ -0,0 +1,34 @@
1
+ const { defaultAnalyzer } = require('./default-analyzer');
2
+
3
+ const ANALYZERS = new Map([
4
+ [defaultAnalyzer.name, defaultAnalyzer],
5
+ ]);
6
+
7
+ function listAnalyzers() {
8
+ return [...ANALYZERS.values()].map((analyzer) => ({
9
+ name: analyzer.name,
10
+ version: analyzer.version,
11
+ }));
12
+ }
13
+
14
+ async function runAnalyzer(name, rootPath, options) {
15
+ const analyzerName = name || 'default';
16
+ const analyzer = ANALYZERS.get(analyzerName);
17
+ if (!analyzer) {
18
+ throw new Error(`Unknown analyzer plugin: ${analyzerName}`);
19
+ }
20
+
21
+ const analysis = await analyzer.analyze(rootPath, options);
22
+ return {
23
+ analyzer: {
24
+ name: analyzer.name,
25
+ version: analyzer.version,
26
+ },
27
+ analysis,
28
+ };
29
+ }
30
+
31
+ module.exports = {
32
+ runAnalyzer,
33
+ listAnalyzers,
34
+ };
@@ -0,0 +1,105 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { summarizeAnalysis } = require('./evidence-summary');
4
+
5
+ function normalizeStringArray(value) {
6
+ return Array.isArray(value)
7
+ ? [...new Set(value
8
+ .filter((entry) => typeof entry === 'string' && entry.trim())
9
+ .map((entry) => entry.trim()))].sort()
10
+ : [];
11
+ }
12
+
13
+ function buildComponentMetadata(analysis) {
14
+ return Array.isArray(analysis?.components)
15
+ ? analysis.components
16
+ .map((component) => ({
17
+ kind: 'component',
18
+ name: component.name || component.originalName || component.filePath || 'unknown',
19
+ path: component.filePath || '',
20
+ type: component.type || 'unknown',
21
+ roleTags: normalizeStringArray(component.roleTags),
22
+ dependencyCount: Array.isArray(component.dependencies) ? component.dependencies.length : 0,
23
+ source: 'analysis.components',
24
+ derivation: 'static-analysis',
25
+ }))
26
+ .sort((left, right) =>
27
+ `${left.path}:${left.name}`.localeCompare(`${right.path}:${right.name}`)
28
+ )
29
+ : [];
30
+ }
31
+
32
+ function buildAgentContext({
33
+ manifest,
34
+ analysis = {},
35
+ prImpact = null,
36
+ warnings = [],
37
+ errors = [],
38
+ mode = 'repository',
39
+ }) {
40
+ const summary = summarizeAnalysis(analysis);
41
+ const artifacts = manifest.artifacts
42
+ .map((entry) => ({
43
+ id: entry.id,
44
+ path: entry.path,
45
+ role: entry.role,
46
+ status: entry.status,
47
+ ...(entry.reason ? { reason: entry.reason } : {}),
48
+ ...(entry.errorCategory ? { errorCategory: entry.errorCategory } : {}),
49
+ }))
50
+ .sort((left, right) => left.path.localeCompare(right.path));
51
+
52
+ const payload = {
53
+ schemaVersion: '1.0',
54
+ generatedBy: 'archscope scan',
55
+ mode: prImpact ? 'pr' : mode,
56
+ partial: artifacts.some((entry) => entry.status === 'partial' || entry.status === 'failed'),
57
+ summary: {
58
+ componentCount: summary.componentCount,
59
+ entryPointCount: summary.entryPointCount,
60
+ totalFilesFound: summary.totalFilesFound,
61
+ languages: Object.fromEntries(summary.languages),
62
+ architectureAreas: Object.fromEntries(summary.areas),
63
+ },
64
+ artifacts,
65
+ components: buildComponentMetadata(analysis),
66
+ readOrder: [...manifest.artifactReadOrder],
67
+ warnings: [...warnings].sort(),
68
+ errors: errors
69
+ .map((error) => ({
70
+ category: error.category,
71
+ artifact: error.artifact,
72
+ message: error.message,
73
+ }))
74
+ .sort((left, right) =>
75
+ `${left.artifact || ''}:${left.category}`.localeCompare(`${right.artifact || ''}:${right.category}`)
76
+ ),
77
+ };
78
+
79
+ if (prImpact) {
80
+ payload.pr = {
81
+ status: prImpact._meta?.status || 'complete',
82
+ changedFiles: prImpact.changedFiles || [],
83
+ changedComponents: prImpact.changedComponents || [],
84
+ blastRadius: prImpact.blastRadius || null,
85
+ reviewerChecks: prImpact.agentSummary?.suggestedReviewerChecks || [],
86
+ };
87
+ payload.base = prImpact.base;
88
+ payload.head = prImpact.head;
89
+ payload.risk = prImpact.risk || null;
90
+ }
91
+
92
+ return payload;
93
+ }
94
+
95
+ function writeAgentContext(filePath, input) {
96
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
97
+ const payload = buildAgentContext(input);
98
+ fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`);
99
+ return payload;
100
+ }
101
+
102
+ module.exports = {
103
+ buildAgentContext,
104
+ writeAgentContext,
105
+ };