gru-ai 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/validate-gate.sh +231 -77
- package/.claude/hooks/validate-project-json.sh +38 -3
- package/.claude/hooks/validate-reviews.sh +50 -11
- package/.claude/skills/directive/SKILL.md +31 -18
- package/.claude/skills/directive/docs/pipeline/00-delegation-and-triage.md +13 -7
- package/.claude/skills/directive/docs/pipeline/01-checkpoint.md +1 -1
- package/.claude/skills/directive/docs/pipeline/02-read-directive.md +24 -1
- package/.claude/skills/directive/docs/pipeline/03-read-context.md +5 -0
- package/.claude/skills/directive/docs/pipeline/04-brainstorm.md +77 -0
- package/.claude/skills/directive/docs/pipeline/04b-clarification.md +222 -0
- package/.claude/skills/directive/docs/pipeline/05-planning.md +21 -9
- package/.claude/skills/directive/docs/pipeline/06-technical-audit.md +32 -23
- package/.claude/skills/directive/docs/pipeline/07-plan-approval.md +53 -37
- package/.claude/skills/directive/docs/pipeline/07b-project-brainstorm.md +45 -5
- package/.claude/skills/directive/docs/pipeline/08-worktree-and-state.md +1 -1
- package/.claude/skills/directive/docs/pipeline/09-execute-projects.md +229 -499
- package/.claude/skills/directive/docs/pipeline/10-wrapup.md +33 -12
- package/.claude/skills/directive/docs/pipeline/11-completion-gate.md +229 -35
- package/.claude/skills/directive/docs/reference/rules/failure-handling.md +7 -3
- package/.claude/skills/directive/docs/reference/rules/phase-definitions.md +10 -2
- package/.claude/skills/directive/docs/reference/rules/scope-and-dod.md +188 -18
- package/.claude/skills/directive/docs/reference/schemas/audit-output.md +8 -4
- package/.claude/skills/directive/docs/reference/schemas/brainstorm-output.md +2 -1
- package/.claude/skills/directive/docs/reference/schemas/checkpoint.md +2 -2
- package/.claude/skills/directive/docs/reference/schemas/directive-json.md +95 -21
- package/.claude/skills/directive/docs/reference/schemas/investigation-output.md +4 -4
- package/.claude/skills/directive/docs/reference/templates/architect-prompt.md +26 -14
- package/.claude/skills/directive/docs/reference/templates/brainstorm-prompt.md +23 -10
- package/.claude/skills/directive/docs/reference/templates/investigator-prompt.md +6 -6
- package/.claude/skills/directive/docs/reference/templates/planner-prompt.md +42 -4
- package/.claude/skills/smoke-test/SKILL.md +84 -0
- package/.claude/skills/smoke-test/run-smoke-test.sh +590 -0
- package/.claude/skills/smoke-test/scenarios.md +34 -0
- package/.claude/skills/walkthrough/SKILL.md +96 -0
- package/README.md +274 -112
- package/cli/templates/gruai.config.json.template +2 -0
- package/dist/assets/GamePage-OJgWSZBK.js +49 -0
- package/dist/assets/{index-Bh01am7W.js → index-BjwyXPf7.js} +5 -5
- package/dist/assets/index-D2wJ_yhU.css +1 -0
- package/dist/assets/metrocity/Character Model.png +0 -0
- package/dist/assets/metrocity/Hairs.png +0 -0
- package/dist/assets/metrocity/Outfit1.png +0 -0
- package/dist/assets/metrocity/Outfit2.png +0 -0
- package/dist/assets/metrocity/Outfit3.png +0 -0
- package/dist/assets/metrocity/Outfit4.png +0 -0
- package/dist/assets/metrocity/Outfit5.png +0 -0
- package/dist/assets/metrocity/Outfit6.png +0 -0
- package/dist/assets/office/anim-bathroom-cabinet.tsx +18 -0
- package/dist/assets/office/atlas.png +0 -0
- package/dist/assets/office/gruai.tmx +364 -0
- package/dist/assets/office/ui.png +0 -0
- package/dist/gruai.tmx +104 -0
- package/dist/index.html +4 -4
- package/dist-cli/commands/init.js +18 -12
- package/dist-cli/commands/scaffold.d.ts +3 -0
- package/dist-cli/commands/scaffold.js +161 -50
- package/dist-cli/commands/start.js +3 -3
- package/dist-cli/commands/validate-init.d.ts +18 -0
- package/dist-cli/commands/validate-init.js +39 -0
- package/dist-cli/index.js +1 -1
- package/dist-cli/lib/roles.js +15 -0
- package/dist-cli/lib/types.d.ts +12 -0
- package/dist-server/server/config.js +13 -2
- package/dist-server/server/index.js +16 -1
- package/dist-server/server/parsers/session-scanner.d.ts +9 -0
- package/dist-server/server/parsers/session-scanner.js +36 -0
- package/dist-server/server/parsers/session-state.d.ts +13 -4
- package/dist-server/server/parsers/session-state.js +24 -55
- package/dist-server/server/platform/claude-code-spawn.js +2 -0
- package/dist-server/server/platform/claude-code.d.ts +4 -0
- package/dist-server/server/platform/claude-code.js +39 -3
- package/dist-server/server/platform/types.d.ts +16 -1
- package/dist-server/server/platform/types.js +1 -1
- package/dist-server/server/types.d.ts +3 -0
- package/dist-server/server/watchers/directive-watcher.d.ts +2 -0
- package/dist-server/server/watchers/directive-watcher.js +74 -13
- package/dist-server/server/watchers/state-watcher.js +3 -0
- package/package.json +3 -2
- package/.claude/skills/directive/docs/pipeline/04-challenge.md +0 -38
- package/.claude/skills/directive/docs/reference/schemas/challenger-output.md +0 -13
- package/.claude/skills/directive/docs/reference/templates/challenger-prompt.md +0 -35
- package/dist/00_Modern_Office_Singles.tsx +0 -4
- package/dist/Game.tiled-project +0 -14
- package/dist/Game.tiled-session +0 -90
- package/dist/Interiors.tsx +0 -4
- package/dist/Interiors_32x32.tsx +0 -4
- package/dist/Office_Design_1.tsx +0 -4
- package/dist/Office_Design_2.tsx +0 -4
- package/dist/assets/GamePage-B2OsBjXm.js +0 -49
- package/dist/assets/characters/char_0.png +0 -0
- package/dist/assets/characters/char_1.png +0 -0
- package/dist/assets/characters/char_10.png +0 -0
- package/dist/assets/characters/char_11.png +0 -0
- package/dist/assets/characters/char_2.png +0 -0
- package/dist/assets/characters/char_3.png +0 -0
- package/dist/assets/characters/char_4.png +0 -0
- package/dist/assets/characters/char_5.png +0 -0
- package/dist/assets/characters/char_6.png +0 -0
- package/dist/assets/characters/char_7.png +0 -0
- package/dist/assets/characters/char_8.png +0 -0
- package/dist/assets/characters/char_9.png +0 -0
- package/dist/assets/index-DCNBE1pw.css +0 -1
- package/dist/assets/office/Interiors.png +0 -0
- package/dist/assets/office/classroom.png +0 -0
- package/dist/assets/office/conference.png +0 -0
- package/dist/assets/office/furniture.png +0 -0
- package/dist/assets/office/generic.png +0 -0
- package/dist/assets/office/kitchen.png +0 -0
- package/dist/assets/office/livingroom.png +0 -0
- package/dist/assets/office/music-sport.png +0 -0
- package/dist/assets/office/room-builder.png +0 -0
- package/dist/classroom.tsx +0 -4
- package/dist/conference.tsx +0 -4
- package/dist/furniture.tsx +0 -4
- package/dist/generic.tsx +0 -4
- package/dist/kitchen.tsx +0 -4
- package/dist/livingroom.tsx +0 -4
- package/dist/music-sport.tsx +0 -4
- package/dist/office.tmx +0 -398
- package/dist/room-builder.tsx +0 -4
- package/dist-server/scripts/intelligence-trends.d.ts +0 -100
- package/dist-server/scripts/intelligence-trends.js +0 -365
- package/dist-server/server/actions/cleanup.d.ts +0 -4
- package/dist-server/server/actions/cleanup.js +0 -30
- package/dist-server/server/parsers/team-parser.d.ts +0 -3
- package/dist-server/server/parsers/team-parser.js +0 -67
- package/dist-server/server/watchers/claude-watcher.d.ts +0 -17
- package/dist-server/server/watchers/claude-watcher.js +0 -130
- package/dist-server/server/watchers/context-watcher.d.ts +0 -22
- package/dist-server/server/watchers/context-watcher.js +0 -125
|
@@ -35,12 +35,20 @@ set -euo pipefail
|
|
|
35
35
|
# path = relative to directive dir, supports {project-id} and {task-id} placeholders
|
|
36
36
|
# required_fields = comma-separated jq paths (for json type only)
|
|
37
37
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
38
|
+
# Pipeline step order (from SKILL.md):
|
|
39
|
+
# triage → checkpoint → read → context → audit → brainstorm → clarification →
|
|
40
|
+
# plan → approve → project-brainstorm → setup → execute → review-gate → wrapup → completion
|
|
41
|
+
#
|
|
42
|
+
# Weight skip rules (from SKILL.md / 00-delegation-and-triage.md):
|
|
43
|
+
# lightweight: skips brainstorm (no C-suite challenges, no separate brainstorm agents)
|
|
44
|
+
# medium: skips brainstorm (COO's inline challenge is included, but no brainstorm step)
|
|
41
45
|
# heavyweight: skips nothing
|
|
42
46
|
# strategic: skips nothing
|
|
43
47
|
#
|
|
48
|
+
# Note: 'challenge' was merged into 'brainstorm' — it is no longer a separate step ID.
|
|
49
|
+
# Clarification is auto-approved (not skipped) for lightweight/medium — it still runs.
|
|
50
|
+
# Approve is auto-approved (not skipped) for lightweight/medium — it still runs.
|
|
51
|
+
#
|
|
44
52
|
# .skip marker convention:
|
|
45
53
|
# For weight-conditional steps, a file named "{step}.skip" in the directive dir
|
|
46
54
|
# satisfies the gate. Example: brainstorm.skip means brainstorm was legitimately
|
|
@@ -49,8 +57,8 @@ set -euo pipefail
|
|
|
49
57
|
|
|
50
58
|
# Steps that can be skipped per weight class
|
|
51
59
|
# Format: SKIP_<WEIGHT> is a space-separated list of skippable steps
|
|
52
|
-
SKIP_lightweight="
|
|
53
|
-
SKIP_medium="
|
|
60
|
+
SKIP_lightweight="brainstorm"
|
|
61
|
+
SKIP_medium="brainstorm"
|
|
54
62
|
SKIP_heavyweight=""
|
|
55
63
|
SKIP_strategic=""
|
|
56
64
|
|
|
@@ -77,14 +85,18 @@ TARGET_STEP="$2"
|
|
|
77
85
|
TASK_ID="${3:-}"
|
|
78
86
|
|
|
79
87
|
# Resolve to repo root for consistent paths
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
# If directive-dir is absolute and contains .context/directives/, derive repo root
|
|
89
|
+
# from it (supports worktrees where git rev-parse returns the wrong root).
|
|
90
|
+
if [[ "$DIRECTIVE_DIR" = /* ]] && [[ "$DIRECTIVE_DIR" == */.context/directives/* ]]; then
|
|
91
|
+
REPO_ROOT="${DIRECTIVE_DIR%%/.context/directives/*}"
|
|
92
|
+
DIRECTIVE_DIR_ABS="$DIRECTIVE_DIR"
|
|
93
|
+
DIRECTIVE_DIR="${DIRECTIVE_DIR#${REPO_ROOT}/}"
|
|
94
|
+
elif [[ ! "$DIRECTIVE_DIR" = /* ]]; then
|
|
95
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
84
96
|
DIRECTIVE_DIR_ABS="${REPO_ROOT}/${DIRECTIVE_DIR}"
|
|
85
97
|
else
|
|
98
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
86
99
|
DIRECTIVE_DIR_ABS="$DIRECTIVE_DIR"
|
|
87
|
-
# Make relative for output
|
|
88
100
|
DIRECTIVE_DIR="${DIRECTIVE_DIR#${REPO_ROOT}/}"
|
|
89
101
|
fi
|
|
90
102
|
|
|
@@ -227,85 +239,106 @@ check_directive_field() {
|
|
|
227
239
|
add_artifact "directive.json:${field_path}"
|
|
228
240
|
}
|
|
229
241
|
|
|
242
|
+
# Check a pipeline step is completed or skipped
|
|
243
|
+
check_step_completed_or_skipped() {
|
|
244
|
+
local step_id="$1" # the step that must be completed
|
|
245
|
+
|
|
246
|
+
# First check if the step has a completed status in directive.json
|
|
247
|
+
local status
|
|
248
|
+
status=$(jq -r ".pipeline.\"${step_id}\".status // empty" "$DIRECTIVE_JSON" 2>/dev/null)
|
|
249
|
+
|
|
250
|
+
if [[ "$status" == "completed" || "$status" == "skipped" ]]; then
|
|
251
|
+
add_artifact "directive.json:pipeline.${step_id}.status"
|
|
252
|
+
return 0
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# Check .skip marker for weight-conditional steps
|
|
256
|
+
if is_skippable "$step_id"; then
|
|
257
|
+
local skip_marker="${DIRECTIVE_DIR_ABS}/${step_id}.skip"
|
|
258
|
+
if [[ -f "$skip_marker" ]]; then
|
|
259
|
+
add_artifact "${DIRECTIVE_DIR}/${step_id}.skip"
|
|
260
|
+
return 0
|
|
261
|
+
fi
|
|
262
|
+
# Skippable but not completed/skipped — still valid (step was legitimately not run)
|
|
263
|
+
add_artifact "${DIRECTIVE_DIR}/${step_id} (skippable, not yet run)"
|
|
264
|
+
return 0
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
add_violation "prerequisite_incomplete" "Prerequisite step '${step_id}' not completed (status: ${status:-missing}) (weight: ${WEIGHT})"
|
|
268
|
+
return 0
|
|
269
|
+
}
|
|
270
|
+
|
|
230
271
|
# ---------------------------------------------------------------------------
|
|
231
272
|
# Gate Definitions: what each step requires before it can run
|
|
232
273
|
# ---------------------------------------------------------------------------
|
|
233
|
-
# The
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
274
|
+
# The gate for each step checks that its PREREQUISITE step is completed.
|
|
275
|
+
#
|
|
276
|
+
# Pipeline order (from SKILL.md):
|
|
277
|
+
# triage → checkpoint → read → context → audit → brainstorm → clarification →
|
|
278
|
+
# plan → approve → project-brainstorm → setup → execute → review-gate → wrapup → completion
|
|
237
279
|
# ---------------------------------------------------------------------------
|
|
238
280
|
|
|
239
|
-
|
|
281
|
+
gate_checkpoint() {
|
|
240
282
|
# Requires: triage completed (weight set in directive.json)
|
|
241
283
|
check_directive_field ".weight" "triage"
|
|
242
284
|
check_directive_field ".pipeline.triage.status" "triage"
|
|
243
285
|
}
|
|
244
286
|
|
|
287
|
+
gate_read() {
|
|
288
|
+
# Requires: checkpoint completed (or skipped — checkpoint just checks for resume)
|
|
289
|
+
# Checkpoint is not skippable per se but is always fast — require triage at minimum
|
|
290
|
+
check_directive_field ".weight" "triage"
|
|
291
|
+
check_directive_field ".pipeline.triage.status" "triage"
|
|
292
|
+
}
|
|
293
|
+
|
|
245
294
|
gate_context() {
|
|
246
295
|
# Requires: read completed
|
|
247
|
-
|
|
296
|
+
check_step_completed_or_skipped "read"
|
|
248
297
|
}
|
|
249
298
|
|
|
250
|
-
|
|
299
|
+
gate_audit() {
|
|
251
300
|
# Requires: context completed
|
|
252
|
-
|
|
301
|
+
check_step_completed_or_skipped "context"
|
|
253
302
|
}
|
|
254
303
|
|
|
255
|
-
|
|
256
|
-
# Requires:
|
|
257
|
-
|
|
304
|
+
gate_brainstorm() {
|
|
305
|
+
# Requires: audit completed (or skipped — audit always runs for all weights)
|
|
306
|
+
check_step_completed_or_skipped "audit"
|
|
258
307
|
}
|
|
259
308
|
|
|
260
|
-
|
|
261
|
-
# Requires:
|
|
262
|
-
|
|
309
|
+
gate_clarification() {
|
|
310
|
+
# Requires: brainstorm completed (or skipped for lightweight/medium)
|
|
311
|
+
check_step_completed_or_skipped "brainstorm"
|
|
263
312
|
}
|
|
264
313
|
|
|
265
|
-
|
|
266
|
-
# Requires:
|
|
267
|
-
#
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if [[ -f "${DIRECTIVE_DIR_ABS}/${f}" ]]; then
|
|
271
|
-
add_artifact "${DIRECTIVE_DIR}/${f}"
|
|
272
|
-
found=true
|
|
273
|
-
break
|
|
274
|
-
fi
|
|
275
|
-
done
|
|
276
|
-
if [[ "$found" == "false" ]]; then
|
|
277
|
-
if is_skippable "audit"; then
|
|
278
|
-
local skip_marker="${DIRECTIVE_DIR_ABS}/audit.skip"
|
|
279
|
-
if [[ -f "$skip_marker" ]]; then
|
|
280
|
-
add_artifact "${DIRECTIVE_DIR}/audit.skip"
|
|
281
|
-
else
|
|
282
|
-
add_violation "missing_artifact" "Missing audit artifact: audit.md (weight: ${WEIGHT})"
|
|
283
|
-
fi
|
|
284
|
-
else
|
|
285
|
-
add_violation "missing_artifact" "Missing audit artifact: audit.md (weight: ${WEIGHT})"
|
|
286
|
-
fi
|
|
287
|
-
fi
|
|
288
|
-
|
|
289
|
-
# Also require plan.json
|
|
290
|
-
check_json "plan.json" "plan" ".projects"
|
|
314
|
+
gate_plan() {
|
|
315
|
+
# Requires: clarification completed (or skipped)
|
|
316
|
+
# Clarification is auto-approved for lightweight/medium but still produces a
|
|
317
|
+
# pipeline.clarification.status entry. Check it completed or was skipped.
|
|
318
|
+
check_step_completed_or_skipped "clarification"
|
|
291
319
|
}
|
|
292
320
|
|
|
293
|
-
|
|
294
|
-
# Requires:
|
|
295
|
-
|
|
321
|
+
gate_approve() {
|
|
322
|
+
# Requires: plan completed (plan.json exists with .projects)
|
|
323
|
+
check_json "plan.json" "plan" ".projects"
|
|
324
|
+
check_step_completed_or_skipped "plan"
|
|
296
325
|
}
|
|
297
326
|
|
|
298
327
|
gate_project_brainstorm() {
|
|
299
328
|
# Requires: approve completed (approve runs before project-brainstorm)
|
|
300
|
-
|
|
329
|
+
check_step_completed_or_skipped "approve"
|
|
301
330
|
# Also require plan.json (input to brainstorm)
|
|
302
331
|
check_json "plan.json" "plan" ".projects"
|
|
303
332
|
}
|
|
304
333
|
|
|
334
|
+
gate_setup() {
|
|
335
|
+
# Requires: project-brainstorm completed
|
|
336
|
+
check_step_completed_or_skipped "project-brainstorm"
|
|
337
|
+
}
|
|
338
|
+
|
|
305
339
|
gate_execute() {
|
|
306
|
-
# Requires:
|
|
307
|
-
|
|
308
|
-
check_directive_field ".pipeline.approve.status" "approve"
|
|
340
|
+
# Requires: setup completed + project.json(s) with tasks exist
|
|
341
|
+
check_step_completed_or_skipped "setup"
|
|
309
342
|
|
|
310
343
|
# Check project.json(s) exist with tasks (output of project-brainstorm)
|
|
311
344
|
local found_project=false
|
|
@@ -318,16 +351,7 @@ gate_execute() {
|
|
|
318
351
|
done
|
|
319
352
|
fi
|
|
320
353
|
if [[ "$found_project" == "false" ]]; then
|
|
321
|
-
|
|
322
|
-
local skip_marker="${DIRECTIVE_DIR_ABS}/project-brainstorm.skip"
|
|
323
|
-
if [[ -f "$skip_marker" ]]; then
|
|
324
|
-
add_artifact "${DIRECTIVE_DIR}/project-brainstorm.skip"
|
|
325
|
-
else
|
|
326
|
-
add_violation "missing_artifact" "No projects/*/project.json found (project-brainstorm not completed)"
|
|
327
|
-
fi
|
|
328
|
-
else
|
|
329
|
-
add_violation "missing_artifact" "No projects/*/project.json found (project-brainstorm not completed)"
|
|
330
|
-
fi
|
|
354
|
+
add_violation "missing_artifact" "No projects/*/project.json found (project-brainstorm not completed)"
|
|
331
355
|
fi
|
|
332
356
|
|
|
333
357
|
# Per-task gate: if task-id provided, check that specific task exists in a project
|
|
@@ -349,12 +373,10 @@ gate_execute() {
|
|
|
349
373
|
fi
|
|
350
374
|
}
|
|
351
375
|
|
|
352
|
-
gate_setup() {
|
|
353
|
-
# Requires: project-brainstorm completed (or skipped for lightweight)
|
|
354
|
-
check_directive_field ".pipeline.approve.status" "approve"
|
|
355
|
-
}
|
|
356
|
-
|
|
357
376
|
gate_review_gate() {
|
|
377
|
+
# Requires: execute completed
|
|
378
|
+
check_step_completed_or_skipped "execute"
|
|
379
|
+
|
|
358
380
|
# Per-task gate: requires build-{task-id}.md exists for the task being reviewed
|
|
359
381
|
if [[ -n "$TASK_ID" ]]; then
|
|
360
382
|
# Find which project this task belongs to
|
|
@@ -381,6 +403,12 @@ gate_review_gate() {
|
|
|
381
403
|
local task_ids
|
|
382
404
|
task_ids=$(jq -r '.tasks[].id' "${pdir}project.json" 2>/dev/null)
|
|
383
405
|
for tid in $task_ids; do
|
|
406
|
+
local task_status
|
|
407
|
+
task_status=$(jq -r --arg tid "$tid" '.tasks[] | select(.id == $tid) | .status' "${pdir}project.json" 2>/dev/null)
|
|
408
|
+
# Skipped/blocked tasks don't need build artifacts
|
|
409
|
+
if [[ "$task_status" == "skipped" || "$task_status" == "blocked" ]]; then
|
|
410
|
+
continue
|
|
411
|
+
fi
|
|
384
412
|
if [[ ! -f "${pdir}build-${tid}.md" ]]; then
|
|
385
413
|
add_violation "missing_artifact" "Missing build artifact: projects/${project_id}/build-${tid}.md"
|
|
386
414
|
else
|
|
@@ -393,7 +421,9 @@ gate_review_gate() {
|
|
|
393
421
|
}
|
|
394
422
|
|
|
395
423
|
gate_wrapup() {
|
|
396
|
-
# Requires: all tasks have review
|
|
424
|
+
# Requires: review-gate completed + all non-skipped tasks have review artifacts
|
|
425
|
+
check_step_completed_or_skipped "review-gate"
|
|
426
|
+
|
|
397
427
|
for pdir in "${DIRECTIVE_DIR_ABS}"/projects/*/; do
|
|
398
428
|
if [[ -f "${pdir}project.json" ]]; then
|
|
399
429
|
local project_id
|
|
@@ -418,22 +448,146 @@ gate_wrapup() {
|
|
|
418
448
|
}
|
|
419
449
|
|
|
420
450
|
gate_completion() {
|
|
421
|
-
# Requires: digest
|
|
422
|
-
|
|
451
|
+
# Requires: wrapup completed + digest file exists at wrapup.digest_path
|
|
452
|
+
check_step_completed_or_skipped "wrapup"
|
|
453
|
+
|
|
454
|
+
# Check that the digest file exists. Wrapup writes to .context/reports/{name}-{date}.md
|
|
455
|
+
# and stores the path in directive.json at wrapup.digest_path
|
|
456
|
+
local digest_path
|
|
457
|
+
digest_path=$(jq -r '.wrapup.digest_path // empty' "$DIRECTIVE_JSON" 2>/dev/null)
|
|
458
|
+
|
|
459
|
+
if [[ -n "$digest_path" ]]; then
|
|
460
|
+
# digest_path may be relative to repo root or absolute
|
|
461
|
+
local full_digest_path
|
|
462
|
+
if [[ "$digest_path" = /* ]]; then
|
|
463
|
+
full_digest_path="$digest_path"
|
|
464
|
+
else
|
|
465
|
+
full_digest_path="${REPO_ROOT}/${digest_path}"
|
|
466
|
+
fi
|
|
467
|
+
if [[ -f "$full_digest_path" ]]; then
|
|
468
|
+
add_artifact "$digest_path"
|
|
469
|
+
else
|
|
470
|
+
add_violation "missing_artifact" "Digest file not found at wrapup.digest_path: ${digest_path}"
|
|
471
|
+
fi
|
|
472
|
+
else
|
|
473
|
+
# Fallback: check for report field (older convention: report = filename without extension)
|
|
474
|
+
local report
|
|
475
|
+
report=$(jq -r '.report // empty' "$DIRECTIVE_JSON" 2>/dev/null)
|
|
476
|
+
if [[ -n "$report" ]]; then
|
|
477
|
+
local report_path=".context/reports/${report}.md"
|
|
478
|
+
local full_report_path="${REPO_ROOT}/${report_path}"
|
|
479
|
+
if [[ -f "$full_report_path" ]]; then
|
|
480
|
+
add_artifact "$report_path"
|
|
481
|
+
else
|
|
482
|
+
add_violation "missing_artifact" "Digest file not found: ${report_path} (from directive.json .report)"
|
|
483
|
+
fi
|
|
484
|
+
else
|
|
485
|
+
add_violation "missing_field" "directive.json missing wrapup.digest_path (digest not written by wrapup step)"
|
|
486
|
+
fi
|
|
487
|
+
fi
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
# ---------------------------------------------------------------------------
|
|
491
|
+
# Pipeline state consistency check
|
|
492
|
+
# ---------------------------------------------------------------------------
|
|
493
|
+
# Verifies that directive.json.current_step matches the target step and that
|
|
494
|
+
# ALL prior steps in the pipeline have status "completed" or "skipped".
|
|
495
|
+
# This catches the failure mode where the orchestrator executes steps but
|
|
496
|
+
# forgets to update directive.json — a mechanical enforcement, not advisory.
|
|
497
|
+
|
|
498
|
+
FULL_PIPELINE_ORDER=(triage checkpoint read context audit brainstorm clarification plan approve project-brainstorm setup execute review-gate wrapup completion)
|
|
499
|
+
|
|
500
|
+
check_pipeline_state_consistency() {
|
|
501
|
+
local target="$1"
|
|
502
|
+
|
|
503
|
+
# 1. Check current_step matches target
|
|
504
|
+
local current_step
|
|
505
|
+
current_step=$(jq -r '.current_step // empty' "$DIRECTIVE_JSON" 2>/dev/null)
|
|
506
|
+
|
|
507
|
+
if [[ -n "$current_step" && "$current_step" != "$target" ]]; then
|
|
508
|
+
# Find positions of current_step and target in pipeline order
|
|
509
|
+
local current_pos=-1
|
|
510
|
+
local target_pos=-1
|
|
511
|
+
for i in "${!FULL_PIPELINE_ORDER[@]}"; do
|
|
512
|
+
if [[ "${FULL_PIPELINE_ORDER[$i]}" == "$current_step" ]]; then
|
|
513
|
+
current_pos=$i
|
|
514
|
+
fi
|
|
515
|
+
if [[ "${FULL_PIPELINE_ORDER[$i]}" == "$target" ]]; then
|
|
516
|
+
target_pos=$i
|
|
517
|
+
fi
|
|
518
|
+
done
|
|
519
|
+
|
|
520
|
+
# Only flag if target is AHEAD of current_step (steps were skipped)
|
|
521
|
+
if [[ $target_pos -gt $current_pos && $current_pos -ge 0 ]]; then
|
|
522
|
+
# Build list of steps between current_step and target that need updating
|
|
523
|
+
local stale_steps=""
|
|
524
|
+
for (( j=current_pos; j<target_pos; j++ )); do
|
|
525
|
+
local step_id="${FULL_PIPELINE_ORDER[$j]}"
|
|
526
|
+
local step_status
|
|
527
|
+
step_status=$(jq -r ".pipeline.\"${step_id}\".status // \"pending\"" "$DIRECTIVE_JSON" 2>/dev/null)
|
|
528
|
+
if [[ "$step_status" != "completed" && "$step_status" != "skipped" ]]; then
|
|
529
|
+
if [[ -n "$stale_steps" ]]; then
|
|
530
|
+
stale_steps="${stale_steps}, ${step_id}"
|
|
531
|
+
else
|
|
532
|
+
stale_steps="${step_id}"
|
|
533
|
+
fi
|
|
534
|
+
fi
|
|
535
|
+
done
|
|
536
|
+
|
|
537
|
+
if [[ -n "$stale_steps" ]]; then
|
|
538
|
+
add_violation "stale_pipeline_state" "directive.json.current_step is '${current_step}' but entering '${target}'. These steps were executed but not updated in directive.json: [${stale_steps}]. Update their pipeline status to 'completed' with output.summary, then set current_step to '${target}'."
|
|
539
|
+
fi
|
|
540
|
+
fi
|
|
541
|
+
fi
|
|
542
|
+
|
|
543
|
+
# 2. Check ALL prior steps have status completed or skipped
|
|
544
|
+
for step_id in "${FULL_PIPELINE_ORDER[@]}"; do
|
|
545
|
+
# Stop when we reach the target step
|
|
546
|
+
if [[ "$step_id" == "$target" ]]; then
|
|
547
|
+
break
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
local step_status
|
|
551
|
+
step_status=$(jq -r ".pipeline.\"${step_id}\".status // \"pending\"" "$DIRECTIVE_JSON" 2>/dev/null)
|
|
552
|
+
|
|
553
|
+
if [[ "$step_status" == "completed" || "$step_status" == "skipped" ]]; then
|
|
554
|
+
continue
|
|
555
|
+
fi
|
|
556
|
+
|
|
557
|
+
# Check if this step is skippable for this weight
|
|
558
|
+
if is_skippable "$step_id"; then
|
|
559
|
+
continue
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
# Step is not completed, not skipped, and not skippable — violation
|
|
563
|
+
local has_output
|
|
564
|
+
has_output=$(jq -r ".pipeline.\"${step_id}\".output.summary // empty" "$DIRECTIVE_JSON" 2>/dev/null)
|
|
565
|
+
if [[ -z "$has_output" ]]; then
|
|
566
|
+
add_violation "step_not_persisted" "Step '${step_id}' has status '${step_status}' with no output.summary. It was likely executed but not persisted to directive.json. Set pipeline.${step_id}.status to 'completed' with output.summary before proceeding."
|
|
567
|
+
else
|
|
568
|
+
add_violation "step_incomplete" "Step '${step_id}' has status '${step_status}' (expected 'completed' or 'skipped')"
|
|
569
|
+
fi
|
|
570
|
+
done
|
|
423
571
|
}
|
|
424
572
|
|
|
573
|
+
# Run consistency check before per-step gates (skip for triage — first step)
|
|
574
|
+
if [[ "$TARGET_STEP" != "triage" ]]; then
|
|
575
|
+
check_pipeline_state_consistency "$TARGET_STEP"
|
|
576
|
+
fi
|
|
577
|
+
|
|
425
578
|
# ---------------------------------------------------------------------------
|
|
426
579
|
# Run the gate for the target step
|
|
427
580
|
# ---------------------------------------------------------------------------
|
|
428
581
|
|
|
429
582
|
case "$TARGET_STEP" in
|
|
430
583
|
triage) ;; # No prerequisites for first step
|
|
584
|
+
checkpoint) gate_checkpoint ;;
|
|
431
585
|
read) gate_read ;;
|
|
432
586
|
context) gate_context ;;
|
|
433
|
-
|
|
587
|
+
audit) gate_audit ;;
|
|
434
588
|
brainstorm) gate_brainstorm ;;
|
|
589
|
+
clarification) gate_clarification ;;
|
|
435
590
|
plan) gate_plan ;;
|
|
436
|
-
audit) gate_audit ;;
|
|
437
591
|
approve) gate_approve ;;
|
|
438
592
|
project-brainstorm) gate_project_brainstorm ;;
|
|
439
593
|
setup) gate_setup ;;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#
|
|
13
13
|
# Exit 0, no output = valid
|
|
14
14
|
# Exit 0, JSON output = validation result (valid: true/false, violations: [...])
|
|
15
|
+
# Exit 1 = validation failure (violations found)
|
|
15
16
|
|
|
16
17
|
set -euo pipefail
|
|
17
18
|
|
|
@@ -44,7 +45,7 @@ violations=()
|
|
|
44
45
|
if [[ ! -f "$PROJECT_PATH" ]]; then
|
|
45
46
|
violations+=("project.json does not exist at ${PROJECT_PATH}. The approve step must create it before execution begins.")
|
|
46
47
|
echo "{\"valid\": false, \"violations\": $(printf '%s\n' "${violations[@]}" | jq -R . | jq -s .)}"
|
|
47
|
-
exit
|
|
48
|
+
exit 1
|
|
48
49
|
fi
|
|
49
50
|
|
|
50
51
|
# Validate required fields
|
|
@@ -102,8 +103,42 @@ for i in $(seq 0 $((TASK_COUNT - 1))); do
|
|
|
102
103
|
fi
|
|
103
104
|
done
|
|
104
105
|
|
|
106
|
+
## DOD completion check: completed tasks must have at least one dod[].met = true
|
|
107
|
+
for i in $(seq 0 $((TASK_COUNT - 1))); do
|
|
108
|
+
TASK_ID=$(jq -r ".tasks[$i].id // \"task-$i\"" "$PROJECT_PATH" 2>/dev/null)
|
|
109
|
+
TASK_STATUS=$(jq -r ".tasks[$i].status // \"\"" "$PROJECT_PATH" 2>/dev/null)
|
|
110
|
+
if [[ "$TASK_STATUS" == "completed" ]]; then
|
|
111
|
+
# Count how many DOD items have met=true
|
|
112
|
+
MET_COUNT=$(jq "[.tasks[$i].dod[]? | select(.met == true)] | length" "$PROJECT_PATH" 2>/dev/null || echo "0")
|
|
113
|
+
TOTAL_DOD=$(jq ".tasks[$i].dod | length" "$PROJECT_PATH" 2>/dev/null || echo "0")
|
|
114
|
+
if [[ "$TOTAL_DOD" -gt 0 && "$MET_COUNT" -eq 0 ]]; then
|
|
115
|
+
violations+=("tasks[$TASK_ID] is marked completed but ALL dod items have met=false — task was completed without DOD verification")
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
## Browser test check: if browser_test is true, warn if no design-review.md
|
|
121
|
+
warnings=()
|
|
122
|
+
BROWSER_TEST=$(jq -r '.browser_test // false' "$PROJECT_PATH" 2>/dev/null)
|
|
123
|
+
if [[ "$BROWSER_TEST" == "true" ]]; then
|
|
124
|
+
PROJECT_DIR=$(dirname "$PROJECT_PATH")
|
|
125
|
+
if [[ ! -f "${PROJECT_DIR}/design-review.md" ]]; then
|
|
126
|
+
warnings+=("browser_test is true but no design-review.md found in ${PROJECT_DIR} — visual review may not have been recorded")
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
|
|
105
130
|
if [[ ${#violations[@]} -eq 0 ]]; then
|
|
106
|
-
|
|
131
|
+
if [[ ${#warnings[@]} -gt 0 ]]; then
|
|
132
|
+
echo "{\"valid\": true, \"violations\": [], \"warnings\": $(printf '%s\n' "${warnings[@]}" | jq -R . | jq -s .)}"
|
|
133
|
+
else
|
|
134
|
+
echo '{"valid": true, "violations": []}'
|
|
135
|
+
fi
|
|
136
|
+
exit 0
|
|
107
137
|
else
|
|
108
|
-
|
|
138
|
+
if [[ ${#warnings[@]} -gt 0 ]]; then
|
|
139
|
+
echo "{\"valid\": false, \"violations\": $(printf '%s\n' "${violations[@]}" | jq -R . | jq -s .), \"warnings\": $(printf '%s\n' "${warnings[@]}" | jq -R . | jq -s .)}"
|
|
140
|
+
else
|
|
141
|
+
echo "{\"valid\": false, \"violations\": $(printf '%s\n' "${violations[@]}" | jq -R . | jq -s .)}"
|
|
142
|
+
fi
|
|
143
|
+
exit 1
|
|
109
144
|
fi
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
# Usage: echo '{"directive_dir":".context/directives/my-dir","project_id":"my-project"}' | ./validate-reviews.sh
|
|
8
8
|
#
|
|
9
9
|
# Checks project.json for completed tasks and verifies that:
|
|
10
|
-
# 1.
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
10
|
+
# 1. Project has reviewers assigned (existing check)
|
|
11
|
+
# 2. Review artifact files exist: review-{task-id}.md in the project directory
|
|
12
|
+
# 3. Task agent != project reviewers (catches self-review)
|
|
13
|
+
# 4. Self-certification heuristic (all DOD met with no review artifact)
|
|
14
14
|
#
|
|
15
15
|
# Exit 0, JSON output = validation result (valid: true/false, violations: [...])
|
|
16
16
|
|
|
@@ -40,10 +40,14 @@ if [[ ! -f "$PROJECT_PATH" ]]; then
|
|
|
40
40
|
exit 0
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
+
# Derive project directory from project.json path
|
|
44
|
+
PROJECT_DIR=$(dirname "$PROJECT_PATH")
|
|
45
|
+
|
|
43
46
|
violations=()
|
|
44
47
|
|
|
45
|
-
# Get project-level reviewers
|
|
46
|
-
|
|
48
|
+
# Get project-level reviewers as newline-separated list (bash 3.2 safe — no associative arrays)
|
|
49
|
+
PROJECT_REVIEWERS_COUNT=$(jq -r '.reviewers | length' "$PROJECT_PATH" 2>/dev/null || echo "0")
|
|
50
|
+
PROJECT_REVIEWERS_LIST=$(jq -r '.reviewers[]?' "$PROJECT_PATH" 2>/dev/null || echo "")
|
|
47
51
|
|
|
48
52
|
TASK_COUNT=$(jq '.tasks | length' "$PROJECT_PATH" 2>/dev/null || echo "0")
|
|
49
53
|
|
|
@@ -60,16 +64,51 @@ for i in $(seq 0 $((TASK_COUNT - 1))); do
|
|
|
60
64
|
HAS_REVIEW_PHASE=$(jq -r ".tasks[$i].phases | if . then (. | index(\"review\")) else null end" "$PROJECT_PATH" 2>/dev/null)
|
|
61
65
|
|
|
62
66
|
if [[ "$HAS_REVIEW_PHASE" != "null" && "$HAS_REVIEW_PHASE" != "" ]]; then
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
|
|
68
|
+
# --- Check 1: Project has reviewers assigned ---
|
|
69
|
+
if [[ "$PROJECT_REVIEWERS_COUNT" -eq 0 ]]; then
|
|
65
70
|
violations+=("Task '${TASK_ID}' is completed with review phase but has NO reviewers assigned")
|
|
66
71
|
fi
|
|
67
72
|
|
|
68
|
-
# Check
|
|
69
|
-
#
|
|
73
|
+
# --- Check 2: Review artifact file exists ---
|
|
74
|
+
# 09-execute-projects.md specifies: review-{task-id}.md or build-{task-id}.md
|
|
75
|
+
REVIEW_ARTIFACT="${PROJECT_DIR}/review-${TASK_ID}.md"
|
|
76
|
+
BUILD_ARTIFACT="${PROJECT_DIR}/build-${TASK_ID}.md"
|
|
77
|
+
if [[ ! -f "$REVIEW_ARTIFACT" && ! -f "$BUILD_ARTIFACT" ]]; then
|
|
78
|
+
violations+=("Task '${TASK_ID}' has no review artifact (expected review-${TASK_ID}.md or build-${TASK_ID}.md in ${PROJECT_DIR})")
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# --- Check 3: Self-review detection (task agent == project reviewer) ---
|
|
82
|
+
# Get the task-level agent (could be a string or array)
|
|
83
|
+
TASK_AGENT=$(jq -r ".tasks[$i].agent | if type == \"array\" then .[0] else . end // empty" "$PROJECT_PATH" 2>/dev/null)
|
|
84
|
+
|
|
85
|
+
if [[ -n "$TASK_AGENT" && -n "$PROJECT_REVIEWERS_LIST" ]]; then
|
|
86
|
+
# Check if the task agent appears in the project reviewers list
|
|
87
|
+
SELF_REVIEW="false"
|
|
88
|
+
while IFS= read -r reviewer; do
|
|
89
|
+
if [[ -n "$reviewer" && "$reviewer" = "$TASK_AGENT" ]]; then
|
|
90
|
+
SELF_REVIEW="true"
|
|
91
|
+
break
|
|
92
|
+
fi
|
|
93
|
+
done <<EOF
|
|
94
|
+
$PROJECT_REVIEWERS_LIST
|
|
95
|
+
EOF
|
|
96
|
+
if [[ "$SELF_REVIEW" = "true" ]]; then
|
|
97
|
+
# Self-review: task builder is also a reviewer — only flag if they are the ONLY reviewer
|
|
98
|
+
if [[ "$PROJECT_REVIEWERS_COUNT" -eq 1 ]]; then
|
|
99
|
+
violations+=("Task '${TASK_ID}' builder ('${TASK_AGENT}') is the only project reviewer — self-review detected")
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# --- Check 4: Self-certification heuristic ---
|
|
105
|
+
# All DOD met + no review artifact = likely self-certified
|
|
70
106
|
DOD_COUNT=$(jq ".tasks[$i].dod | length" "$PROJECT_PATH" 2>/dev/null || echo "0")
|
|
71
107
|
DOD_MET=$(jq "[.tasks[$i].dod[] | select(.met == true)] | length" "$PROJECT_PATH" 2>/dev/null || echo "0")
|
|
72
|
-
|
|
108
|
+
|
|
109
|
+
if [[ "$DOD_COUNT" -gt 0 && "$DOD_MET" -eq "$DOD_COUNT" && ! -f "$REVIEW_ARTIFACT" ]]; then
|
|
110
|
+
violations+=("Task '${TASK_ID}' has ALL DOD criteria met but no review artifact — possible self-certification")
|
|
111
|
+
fi
|
|
73
112
|
|
|
74
113
|
# Check for VISUAL GATE criteria that require browser verification
|
|
75
114
|
VISUAL_GATES=$(jq -r "[.tasks[$i].dod[] | select(.criterion | test(\"VISUAL GATE|browser screenshot|verified by human\"; \"i\")) | select(.met == true)] | length" "$PROJECT_PATH" 2>/dev/null || echo "0")
|