ai-core-framework 0.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 (103) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/.codex-plugin/plugin.json +35 -0
  3. package/.cursor-plugin/plugin.json +22 -0
  4. package/README.md +173 -0
  5. package/bin/ai-core-framework.js +110 -0
  6. package/core/README.md +162 -0
  7. package/core/agents/README.md +32 -0
  8. package/core/agents/business-analyst.md +269 -0
  9. package/core/agents/developer.md +375 -0
  10. package/core/agents/qa-tester.md +477 -0
  11. package/core/agents/scrum-master.md +136 -0
  12. package/core/agents/tech-lead.md +345 -0
  13. package/core/config/backlog.schema.json +38 -0
  14. package/core/config/docs-policy.default.json +37 -0
  15. package/core/config/release.schema.json +120 -0
  16. package/core/config/ticket.schema.json +253 -0
  17. package/core/rules/00-global-rules.md +373 -0
  18. package/core/rules/01-git-workflow.md +388 -0
  19. package/core/rules/02-code-quality.md +77 -0
  20. package/core/rules/03-security.md +78 -0
  21. package/core/rules/04-documentation.md +72 -0
  22. package/core/rules/05-testing-mandatory.md +374 -0
  23. package/core/rules/06-approval-gates.md +388 -0
  24. package/core/rules/07-definition-of-ready.md +112 -0
  25. package/core/rules/08-definition-of-done.md +149 -0
  26. package/core/scripts/ai-core.sh +456 -0
  27. package/core/scripts/generate-views.sh +210 -0
  28. package/core/scripts/install-codex-prompts.sh +127 -0
  29. package/core/scripts/log-user-request.sh +113 -0
  30. package/core/scripts/setup-project.sh +183 -0
  31. package/core/scripts/sync-platforms.sh +322 -0
  32. package/core/scripts/validate-audit-log.sh +73 -0
  33. package/core/scripts/validate-docs.sh +365 -0
  34. package/core/scripts/validate-permissions.sh +132 -0
  35. package/core/scripts/validate-state.sh +611 -0
  36. package/core/scripts/workflow.sh +513 -0
  37. package/core/skills/README.md +21 -0
  38. package/core/skills/ai-core-commands/SKILL.md +86 -0
  39. package/core/skills/brainstorming/SKILL.md +40 -0
  40. package/core/skills/development-implement-task/SKILL.md +308 -0
  41. package/core/skills/executing-ticket/SKILL.md +28 -0
  42. package/core/skills/git-branch-status/SKILL.md +56 -0
  43. package/core/skills/git-cleanup-branches/SKILL.md +57 -0
  44. package/core/skills/git-scan-untracked/SKILL.md +50 -0
  45. package/core/skills/meta-generate-views/SKILL.md +54 -0
  46. package/core/skills/meta-request-log/SKILL.md +61 -0
  47. package/core/skills/meta-sprint-report/SKILL.md +59 -0
  48. package/core/skills/meta-sync-platforms/SKILL.md +53 -0
  49. package/core/skills/meta-ticket-health/SKILL.md +61 -0
  50. package/core/skills/meta-validate-audit-log/SKILL.md +42 -0
  51. package/core/skills/meta-validate-docs/SKILL.md +58 -0
  52. package/core/skills/meta-validate-permissions/SKILL.md +53 -0
  53. package/core/skills/meta-validate-state/SKILL.md +58 -0
  54. package/core/skills/planning-analyze-requirements/SKILL.md +471 -0
  55. package/core/skills/planning-backlog-status/SKILL.md +57 -0
  56. package/core/skills/planning-document-existing-requirements/SKILL.md +246 -0
  57. package/core/skills/planning-estimate-task/SKILL.md +60 -0
  58. package/core/skills/planning-groom-ticket/SKILL.md +442 -0
  59. package/core/skills/planning-mark-ready/SKILL.md +111 -0
  60. package/core/skills/planning-plan-refactor/SKILL.md +66 -0
  61. package/core/skills/planning-plan-sprint/SKILL.md +112 -0
  62. package/core/skills/planning-prioritize-backlog/SKILL.md +62 -0
  63. package/core/skills/planning-write-plan/SKILL.md +68 -0
  64. package/core/skills/project-detect-stack/SKILL.md +71 -0
  65. package/core/skills/project-discover-codebase/SKILL.md +74 -0
  66. package/core/skills/project-setup-project/SKILL.md +113 -0
  67. package/core/skills/qa-bug-status/SKILL.md +52 -0
  68. package/core/skills/qa-report-bug/SKILL.md +518 -0
  69. package/core/skills/qa-smoke-test/SKILL.md +387 -0
  70. package/core/skills/qa-triage-bug/SKILL.md +62 -0
  71. package/core/skills/qa-verify-fix/SKILL.md +446 -0
  72. package/core/skills/release-hotfix/SKILL.md +117 -0
  73. package/core/skills/release-release/SKILL.md +123 -0
  74. package/core/skills/release-rollback/SKILL.md +62 -0
  75. package/core/skills/review-create-pr/SKILL.md +418 -0
  76. package/core/skills/review-merge-pr/SKILL.md +425 -0
  77. package/core/skills/review-techlead-review/SKILL.md +547 -0
  78. package/core/skills/using-ai-core/SKILL.md +72 -0
  79. package/core/skills/verification-before-done/SKILL.md +35 -0
  80. package/core/skills/writing-implementation-plan/SKILL.md +45 -0
  81. package/core/templates/ci/ai-core-governance.yml +112 -0
  82. package/core/templates/ci/node-pnpm.yml +35 -0
  83. package/core/templates/pm/retrospective-template.md +47 -0
  84. package/core/templates/pm/sprint-plan-template.md +45 -0
  85. package/core/templates/pr/pull-request-template.md +247 -0
  86. package/core/templates/project/CODEOWNERS +11 -0
  87. package/core/templates/project/docs-policy.json +3 -0
  88. package/core/templates/project/project-config.yaml +137 -0
  89. package/core/templates/project/project-structure.yaml +76 -0
  90. package/core/templates/qa/bug-report-template.md +371 -0
  91. package/core/templates/qa/test-plan-template.md +57 -0
  92. package/core/templates/release/release-record-template.json +67 -0
  93. package/core/templates/requirements/PRD-template.md +58 -0
  94. package/core/templates/requirements/user-story-template.md +381 -0
  95. package/core/templates/technical/ADR-template.md +46 -0
  96. package/core/templates/technical/refactor-plan-template.md +84 -0
  97. package/core/templates/technical/tech-design-template.md +71 -0
  98. package/core/workflows/bug-lifecycle.md +56 -0
  99. package/core/workflows/feature-lifecycle.md +347 -0
  100. package/core/workflows/hotfix-lifecycle.md +65 -0
  101. package/core/workflows/sprint-lifecycle.md +56 -0
  102. package/lib/install-codex.js +85 -0
  103. package/package.json +36 -0
@@ -0,0 +1,456 @@
1
+ #!/usr/bin/env bash
2
+ # core/scripts/ai-core.sh
3
+ #
4
+ # Lightweight command runner for executable AI Core commands.
5
+ #
6
+ # User-facing usage is chat-first: type `/command ...` in the AI chat.
7
+ # This script is the internal executable runner used by AI tools and CI.
8
+
9
+ set -euo pipefail
10
+
11
+ RED='\033[0;31m'
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ BLUE='\033[0;34m'
15
+ NC='\033[0m'
16
+ AGENT_OVERRIDE=""
17
+
18
+ log_info() { echo -e "${BLUE}i${NC} $1"; }
19
+ log_pass() { echo -e "${GREEN}+${NC} $1"; }
20
+ log_warn() { echo -e "${YELLOW}!${NC} $1"; }
21
+ log_fail() { echo -e "${RED}x${NC} $1"; }
22
+
23
+ AI_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
24
+ PROJECT_ROOT="$(dirname "$AI_CORE_DIR")"
25
+
26
+ usage() {
27
+ cat <<'EOF'
28
+ Usage:
29
+ In AI chat:
30
+ /command [args...]
31
+ guide /command [args...]
32
+ next TICKET-XXX
33
+
34
+ Internal runner form:
35
+ bash core/scripts/ai-core.sh [--agent role] run /command [args...]
36
+ bash core/scripts/ai-core.sh [--agent role] guide /command [args...]
37
+ bash core/scripts/ai-core.sh [--agent role] next TICKET-XXX
38
+ bash core/scripts/ai-core.sh list
39
+
40
+ Environment:
41
+ AI_AGENT=<agent-role> Optional internal override. Runner auto-selects owner_agent by default.
42
+
43
+ Chat examples:
44
+ /validate-state
45
+ guide /groom-ticket TICKET-001 5
46
+ next TICKET-001
47
+ /validate-docs --base origin/main
48
+ EOF
49
+ }
50
+
51
+ normalize_agent() {
52
+ printf '%s' "$1" | sed 's/-agent$//'
53
+ }
54
+
55
+ command_name() {
56
+ printf '%s' "$1" | sed 's#^/##'
57
+ }
58
+
59
+ command_file_for() {
60
+ local command="$1"
61
+ local name
62
+ name=$(command_name "$command")
63
+ find "$AI_CORE_DIR/commands" -name "$name.md" -type f | head -1
64
+ }
65
+
66
+ allowed_agents_for_command() {
67
+ local file="$1"
68
+
69
+ awk '
70
+ /^owner_agent:/ {
71
+ gsub(/"/, "", $2);
72
+ print $2;
73
+ }
74
+ /^requires_agents:/ {
75
+ in_requires=1;
76
+ next;
77
+ }
78
+ in_requires && /^ - / {
79
+ agent=$2;
80
+ gsub(/"/, "", agent);
81
+ print agent;
82
+ next;
83
+ }
84
+ in_requires && /^[^ ]/ {
85
+ in_requires=0;
86
+ }
87
+ ' "$file" | sort -u
88
+ }
89
+
90
+ owner_agent_for_command() {
91
+ local file="$1"
92
+ awk '/^owner_agent:/ { gsub(/"/, "", $2); print $2; exit }' "$file"
93
+ }
94
+
95
+ assert_permission() {
96
+ local command="$1"
97
+ local file="$2"
98
+ local agent="${AGENT_OVERRIDE:-${AI_AGENT:-}}"
99
+
100
+ if [ -z "$agent" ]; then
101
+ agent=$(owner_agent_for_command "$file")
102
+ fi
103
+
104
+ if [ -z "$agent" ]; then
105
+ local first_allowed
106
+ first_allowed=$(allowed_agents_for_command "$file" | head -1)
107
+ agent="$first_allowed"
108
+ fi
109
+
110
+ if [ -z "$agent" ]; then
111
+ log_fail "Could not determine agent for $command"
112
+ log_warn "Check owner_agent/requires_agents metadata for $command"
113
+ exit 1
114
+ fi
115
+
116
+ local role allowed
117
+ role=$(normalize_agent "$agent")
118
+ allowed=$(allowed_agents_for_command "$file")
119
+
120
+ if ! printf '%s\n' "$allowed" | grep -qx "$role"; then
121
+ log_fail "$agent is not allowed to run $command"
122
+ log_fail "Allowed: $(printf '%s' "$allowed" | tr '\n' ' ')"
123
+ exit 1
124
+ fi
125
+
126
+ export AI_AGENT="$role"
127
+ }
128
+
129
+ run_command() {
130
+ local command="$1"
131
+ shift || true
132
+
133
+ local file
134
+ file=$(command_file_for "$command")
135
+ if [ -z "$file" ]; then
136
+ log_fail "Unknown command: $command"
137
+ exit 1
138
+ fi
139
+
140
+ assert_permission "$command" "$file"
141
+
142
+ cd "$PROJECT_ROOT"
143
+
144
+ case "$(command_name "$command")" in
145
+ setup-project)
146
+ bash "$AI_CORE_DIR/scripts/setup-project.sh" "$@"
147
+ ;;
148
+ sync-platforms)
149
+ bash "$AI_CORE_DIR/scripts/sync-platforms.sh" "$@"
150
+ ;;
151
+ validate-state)
152
+ bash "$AI_CORE_DIR/scripts/validate-state.sh" "$@"
153
+ ;;
154
+ validate-docs)
155
+ bash "$AI_CORE_DIR/scripts/validate-docs.sh" "$@"
156
+ ;;
157
+ validate-permissions)
158
+ bash "$AI_CORE_DIR/scripts/validate-permissions.sh" "$@"
159
+ ;;
160
+ validate-audit-log)
161
+ bash "$AI_CORE_DIR/scripts/validate-audit-log.sh" "$@"
162
+ ;;
163
+ generate-views)
164
+ bash "$AI_CORE_DIR/scripts/generate-views.sh" "$@"
165
+ ;;
166
+ analyze-requirements|groom-ticket|write-plan|mark-ready|implement-task|create-pr|merge-pr|smoke-test|release)
167
+ bash "$AI_CORE_DIR/scripts/workflow.sh" "$(command_name "$command")" "$@"
168
+ ;;
169
+ *)
170
+ log_pass "Permission granted for $command"
171
+ log_warn "No executable handler exists yet. Follow command spec: $file"
172
+ ;;
173
+ esac
174
+ }
175
+
176
+ ticket_file() {
177
+ printf '%s/project/tickets/%s.json' "$PROJECT_ROOT" "$1"
178
+ }
179
+
180
+ latest_audit_field() {
181
+ local field="$1"
182
+ local audit_file="$PROJECT_ROOT/project/audit-log.jsonl"
183
+ if [ ! -s "$audit_file" ]; then
184
+ printf ''
185
+ return 0
186
+ fi
187
+ tail -1 "$audit_file" | jq -r ".$field // empty" 2>/dev/null || true
188
+ }
189
+
190
+ ticket_from_args_or_audit() {
191
+ for arg in "$@"; do
192
+ if [[ "$arg" =~ ^TICKET-[0-9]{3,}$ ]]; then
193
+ printf '%s' "$arg"
194
+ return 0
195
+ fi
196
+ done
197
+ latest_audit_field "ticket_id"
198
+ }
199
+
200
+ next_step_for_status() {
201
+ local ticket_id="$1"
202
+ local file status
203
+ file=$(ticket_file "$ticket_id")
204
+
205
+ if [ ! -f "$file" ]; then
206
+ log_fail "Ticket not found: $ticket_id"
207
+ return 1
208
+ fi
209
+
210
+ status=$(jq -r '.status // empty' "$file")
211
+ case "$status" in
212
+ DRAFT)
213
+ printf 'tech-lead|/groom-ticket %s <story_points>|DRAFT -> GROOMED' "$ticket_id"
214
+ ;;
215
+ GROOMED)
216
+ if jq -e '.implementation_plan_path // empty' "$file" >/dev/null 2>&1; then
217
+ printf 'scrum-master|/mark-ready %s|GROOMED -> READY' "$ticket_id"
218
+ else
219
+ printf 'tech-lead|/write-plan %s|Create implementation plan before READY' "$ticket_id"
220
+ fi
221
+ ;;
222
+ READY)
223
+ if jq -e '.implementation_plan_path // empty' "$file" >/dev/null 2>&1; then
224
+ printf 'developer|/implement-task %s|READY -> IN_PROGRESS' "$ticket_id"
225
+ else
226
+ printf 'tech-lead|/write-plan %s|Create implementation plan before implementation' "$ticket_id"
227
+ fi
228
+ ;;
229
+ IN_PROGRESS)
230
+ printf 'developer|/create-pr %s <pr_url>|IN_PROGRESS -> IN_REVIEW' "$ticket_id"
231
+ ;;
232
+ IN_REVIEW)
233
+ printf 'tech-lead|/merge-pr %s|IN_REVIEW -> QA' "$ticket_id"
234
+ ;;
235
+ QA)
236
+ printf 'qa-tester|/smoke-test %s|QA -> DONE' "$ticket_id"
237
+ ;;
238
+ DONE)
239
+ printf 'scrum-master|/generate-views|DONE -> views/release readiness'
240
+ ;;
241
+ BLOCKED)
242
+ printf 'scrum-master|/ticket-health %s|BLOCKED -> review blocker' "$ticket_id"
243
+ ;;
244
+ CANCELLED)
245
+ printf 'none|stop|CANCELLED -> stop'
246
+ ;;
247
+ *)
248
+ printf 'none|stop|Unknown status: %s' "$status"
249
+ ;;
250
+ esac
251
+ }
252
+
253
+ print_next_step() {
254
+ local ticket_id="$1"
255
+ local next agent command description
256
+ next=$(next_step_for_status "$ticket_id") || return 1
257
+ IFS='|' read -r agent command description <<< "$next"
258
+
259
+ echo ""
260
+ echo "Suggested next step:"
261
+ if [ "$agent" = "none" ] || [ "$command" = "stop" ]; then
262
+ echo " $description"
263
+ else
264
+ echo " Chat: $command"
265
+ echo " # $description"
266
+ fi
267
+ }
268
+
269
+ run_validation_quiet() {
270
+ local label="$1"
271
+ shift
272
+
273
+ if "$@" >/tmp/ai-core-guide-validation.log 2>&1; then
274
+ echo "- $label: pass"
275
+ else
276
+ echo "- $label: fail"
277
+ sed 's/^/ /' /tmp/ai-core-guide-validation.log | tail -20
278
+ fi
279
+ }
280
+
281
+ guide_command() {
282
+ local command="$1"
283
+ shift || true
284
+
285
+ local before_status="" after_status="" ticket_id="" before_audit_hash="" after_audit_hash=""
286
+ local before_file="" after_file=""
287
+
288
+ cd "$PROJECT_ROOT"
289
+ ticket_id=$(ticket_from_args_or_audit "$@")
290
+ if [ -n "$ticket_id" ]; then
291
+ before_file=$(ticket_file "$ticket_id")
292
+ if [ -f "$before_file" ]; then
293
+ before_status=$(jq -r '.status // empty' "$before_file")
294
+ fi
295
+ fi
296
+ before_audit_hash=$(latest_audit_field "hash")
297
+
298
+ run_command "$command" "$@"
299
+
300
+ ticket_id=$(ticket_from_args_or_audit "$@")
301
+ after_audit_hash=$(latest_audit_field "hash")
302
+ if [ -n "$ticket_id" ]; then
303
+ after_file=$(ticket_file "$ticket_id")
304
+ if [ -f "$after_file" ]; then
305
+ after_status=$(jq -r '.status // empty' "$after_file")
306
+ fi
307
+ fi
308
+
309
+ echo ""
310
+ echo "========================================================"
311
+ echo " Guided workflow report"
312
+ echo "========================================================"
313
+ echo ""
314
+ echo "Completed: $command $*"
315
+
316
+ if [ -n "$ticket_id" ]; then
317
+ echo "Ticket: $ticket_id"
318
+ if [ -n "$before_status" ] || [ -n "$after_status" ]; then
319
+ echo "State: ${before_status:-new} -> ${after_status:-unknown}"
320
+ fi
321
+ fi
322
+
323
+ echo ""
324
+ echo "Updated:"
325
+ if [ -n "$ticket_id" ] && [ -f "$(ticket_file "$ticket_id")" ]; then
326
+ echo "- project/tickets/$ticket_id.json"
327
+ fi
328
+ if [ "$before_audit_hash" != "$after_audit_hash" ] && [ -s "project/audit-log.jsonl" ]; then
329
+ echo "- project/audit-log.jsonl"
330
+ fi
331
+ case "$(command_name "$command")" in
332
+ generate-views)
333
+ echo "- project/views/*.md"
334
+ ;;
335
+ smoke-test)
336
+ echo "- project/test-runs/${ticket_id}-*.md"
337
+ ;;
338
+ release)
339
+ echo "- project/releases/*.json"
340
+ ;;
341
+ esac
342
+
343
+ echo ""
344
+ echo "Validation:"
345
+ run_validation_quiet "state" bash "$AI_CORE_DIR/scripts/validate-state.sh"
346
+ run_validation_quiet "permissions" bash "$AI_CORE_DIR/scripts/validate-permissions.sh"
347
+ run_validation_quiet "docs" bash "$AI_CORE_DIR/scripts/validate-docs.sh"
348
+ run_validation_quiet "audit-log" bash "$AI_CORE_DIR/scripts/validate-audit-log.sh"
349
+
350
+ if [ -n "$ticket_id" ] && [ -f "$(ticket_file "$ticket_id")" ]; then
351
+ print_next_step "$ticket_id"
352
+
353
+ local next agent next_command description answer
354
+ next=$(next_step_for_status "$ticket_id")
355
+ IFS='|' read -r agent next_command description <<< "$next"
356
+ if [ "$agent" != "none" ] && [ "$next_command" != "stop" ]; then
357
+ echo ""
358
+ if [[ "$next_command" == *"<"* ]]; then
359
+ log_info "Next step needs user-provided argument(s); not auto-running."
360
+ return 0
361
+ fi
362
+ if [ -t 0 ]; then
363
+ printf 'Proceed with next step now? [y/N] '
364
+ read -r answer
365
+ case "$answer" in
366
+ y|Y|yes|YES)
367
+ bash "$AI_CORE_DIR/scripts/ai-core.sh" --agent "$agent" run $next_command
368
+ ;;
369
+ *)
370
+ log_info "Stopped after guided report."
371
+ ;;
372
+ esac
373
+ else
374
+ log_info "Non-interactive shell detected; not auto-running next step."
375
+ fi
376
+ fi
377
+ fi
378
+ }
379
+
380
+ next_command() {
381
+ local ticket_id="${1:-}"
382
+ if [ -z "$ticket_id" ]; then
383
+ log_fail "Usage in chat: next TICKET-XXX"
384
+ exit 2
385
+ fi
386
+ cd "$PROJECT_ROOT"
387
+ print_next_step "$ticket_id"
388
+ }
389
+
390
+ list_commands() {
391
+ find "$AI_CORE_DIR/commands" -name '*.md' -type f |
392
+ grep -v '/README.md$' |
393
+ sed "s#^$AI_CORE_DIR/commands/##" |
394
+ sed 's#/# #g; s#\.md$##' |
395
+ awk '{ print "/" $NF }' |
396
+ sort
397
+ }
398
+
399
+ main() {
400
+ while [ $# -gt 0 ]; do
401
+ case "${1:-}" in
402
+ --agent)
403
+ AGENT_OVERRIDE="${2:-}"
404
+ if [ -z "$AGENT_OVERRIDE" ]; then
405
+ log_fail "--agent requires a role"
406
+ exit 2
407
+ fi
408
+ shift 2
409
+ ;;
410
+ --agent=*)
411
+ AGENT_OVERRIDE="${1#--agent=}"
412
+ shift
413
+ ;;
414
+ *)
415
+ break
416
+ ;;
417
+ esac
418
+ done
419
+
420
+ local subcommand="${1:-}"
421
+ case "$subcommand" in
422
+ run)
423
+ shift
424
+ if [ $# -lt 1 ]; then
425
+ usage
426
+ exit 2
427
+ fi
428
+ run_command "$@"
429
+ ;;
430
+ guide)
431
+ shift
432
+ if [ $# -lt 1 ]; then
433
+ usage
434
+ exit 2
435
+ fi
436
+ guide_command "$@"
437
+ ;;
438
+ next)
439
+ shift
440
+ next_command "$@"
441
+ ;;
442
+ list)
443
+ list_commands
444
+ ;;
445
+ -h|--help|help|"")
446
+ usage
447
+ ;;
448
+ *)
449
+ log_fail "Unknown subcommand: $subcommand"
450
+ usage
451
+ exit 2
452
+ ;;
453
+ esac
454
+ }
455
+
456
+ main "$@"
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/generate-views.sh
3
+ #
4
+ # Generates human-readable views from project without changing canonical state.
5
+ # Canonical sources:
6
+ # - project/backlog/backlog.json for backlog order
7
+ # - project/tickets/*.json for ticket details and state history
8
+
9
+ set -euo pipefail
10
+
11
+ STATE_DIR="project"
12
+ BACKLOG_FILE="$STATE_DIR/backlog/backlog.json"
13
+ TICKETS_DIR="$STATE_DIR/tickets"
14
+ VIEWS_DIR="$STATE_DIR/views"
15
+
16
+ RED='\033[0;31m'
17
+ GREEN='\033[0;32m'
18
+ YELLOW='\033[1;33m'
19
+ BLUE='\033[0;34m'
20
+ NC='\033[0m'
21
+
22
+ log_info() { echo -e "${BLUE}ℹ${NC} $1"; }
23
+ log_pass() { echo -e "${GREEN}✓${NC} $1"; }
24
+ log_warn() { echo -e "${YELLOW}⚠${NC} $1"; }
25
+ log_fail() { echo -e "${RED}✗${NC} $1"; }
26
+
27
+ require_jq() {
28
+ if ! command -v jq >/dev/null 2>&1; then
29
+ log_fail "jq not installed. Install with: brew install jq"
30
+ exit 2
31
+ fi
32
+ }
33
+
34
+ get_ticket_field() {
35
+ local ticket_id="$1"
36
+ local field="$2"
37
+ local file="$TICKETS_DIR/$ticket_id.json"
38
+
39
+ if [ ! -f "$file" ]; then
40
+ printf '%s' "MISSING"
41
+ return 0
42
+ fi
43
+
44
+ jq -r "$field // \"\"" "$file"
45
+ }
46
+
47
+ ticket_count_by_status() {
48
+ local status="$1"
49
+ find "$TICKETS_DIR" -name 'TICKET-*.json' -type f -print0 2>/dev/null |
50
+ while IFS= read -r -d '' file; do
51
+ if [ "$(jq -r '.status' "$file")" = "$status" ]; then
52
+ echo 1
53
+ fi
54
+ done | wc -l | tr -d ' '
55
+ }
56
+
57
+ write_header() {
58
+ local file="$1"
59
+ local title="$2"
60
+ {
61
+ echo "# $title"
62
+ echo ""
63
+ echo "Generated from project. Do not edit this file as source of truth."
64
+ echo ""
65
+ echo "Generated at: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
66
+ echo ""
67
+ } > "$file"
68
+ }
69
+
70
+ main() {
71
+ require_jq
72
+
73
+ if [ ! -d "$STATE_DIR" ]; then
74
+ log_fail "Missing $STATE_DIR. Run /setup-project first."
75
+ exit 1
76
+ fi
77
+
78
+ if [ ! -f "$BACKLOG_FILE" ]; then
79
+ log_fail "Missing $BACKLOG_FILE"
80
+ exit 1
81
+ fi
82
+
83
+ mkdir -p "$VIEWS_DIR"
84
+
85
+ log_info "Generating views into $VIEWS_DIR"
86
+
87
+ local backlog_view="$VIEWS_DIR/backlog.md"
88
+ write_header "$backlog_view" "Backlog View"
89
+ {
90
+ echo "| Rank | Ticket | Priority | Status | Title | Epic |"
91
+ echo "|------|--------|----------|--------|-------|------|"
92
+ jq -r '.items[] | [.rank, .ticket_id, (.priority // ""), (.epic // "")] | @tsv' "$BACKLOG_FILE" |
93
+ while IFS=$'\t' read -r rank ticket_id priority epic; do
94
+ status=$(get_ticket_field "$ticket_id" '.status')
95
+ title=$(get_ticket_field "$ticket_id" '.title')
96
+ echo "| $rank | $ticket_id | $priority | $status | $title | $epic |"
97
+ done
98
+ } >> "$backlog_view"
99
+ log_pass "$backlog_view"
100
+
101
+ for status in READY IN_PROGRESS IN_REVIEW QA BLOCKED; do
102
+ local lower
103
+ lower=$(printf '%s' "$status" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
104
+ local view="$VIEWS_DIR/$lower.md"
105
+ write_header "$view" "$status Tickets"
106
+ {
107
+ echo "| Ticket | Priority | Title | Assignee | Sprint |"
108
+ echo "|--------|----------|-------|----------|--------|"
109
+ find "$TICKETS_DIR" -name 'TICKET-*.json' -type f -print0 2>/dev/null |
110
+ while IFS= read -r -d '' file; do
111
+ if [ "$(jq -r '.status' "$file")" = "$status" ]; then
112
+ ticket_id=$(jq -r '.id' "$file")
113
+ priority=$(jq -r '.priority // ""' "$file")
114
+ title=$(jq -r '.title // ""' "$file")
115
+ assignee=$(jq -r '.assignee // ""' "$file")
116
+ sprint_id=$(jq -r '.sprint_id // ""' "$file")
117
+ echo "| $ticket_id | $priority | $title | $assignee | $sprint_id |"
118
+ fi
119
+ done
120
+ } >> "$view"
121
+ log_pass "$view"
122
+ done
123
+
124
+ local dashboard_view="$VIEWS_DIR/dashboard.md"
125
+ write_header "$dashboard_view" "Project Dashboard"
126
+ {
127
+ echo "## Flow Summary"
128
+ echo ""
129
+ echo "| State | Count |"
130
+ echo "|-------|-------|"
131
+ for status in DRAFT GROOMED READY IN_PROGRESS IN_REVIEW QA DONE BLOCKED CANCELLED; do
132
+ echo "| $status | $(ticket_count_by_status "$status") |"
133
+ done
134
+ echo ""
135
+ echo "## WIP"
136
+ echo ""
137
+ echo "| Ticket | Status | Priority | Assignee | Sprint | Title |"
138
+ echo "|--------|--------|----------|----------|--------|-------|"
139
+ find "$TICKETS_DIR" -name 'TICKET-*.json' -type f -print0 2>/dev/null |
140
+ while IFS= read -r -d '' file; do
141
+ status=$(jq -r '.status' "$file")
142
+ if [[ "$status" =~ ^(IN_PROGRESS|IN_REVIEW|QA|BLOCKED)$ ]]; then
143
+ ticket_id=$(jq -r '.id' "$file")
144
+ priority=$(jq -r '.priority // ""' "$file")
145
+ assignee=$(jq -r '.assignee // ""' "$file")
146
+ sprint_id=$(jq -r '.sprint_id // ""' "$file")
147
+ title=$(jq -r '.title // ""' "$file")
148
+ echo "| $ticket_id | $status | $priority | $assignee | $sprint_id | $title |"
149
+ fi
150
+ done
151
+ echo ""
152
+ echo "## Epic Progress"
153
+ echo ""
154
+ echo "| Epic | Total | Done | In Progress | Blocked |"
155
+ echo "|------|-------|------|-------------|---------|"
156
+ jq -sr '
157
+ group_by(.epic // "NO_EPIC")[] |
158
+ {
159
+ epic: (.[0].epic // "NO_EPIC"),
160
+ total: length,
161
+ done: map(select(.status == "DONE")) | length,
162
+ wip: map(select(.status == "IN_PROGRESS" or .status == "IN_REVIEW" or .status == "QA")) | length,
163
+ blocked: map(select(.status == "BLOCKED")) | length
164
+ } |
165
+ [.epic, .total, .done, .wip, .blocked] | @tsv
166
+ ' "$TICKETS_DIR"/TICKET-*.json 2>/dev/null |
167
+ while IFS=$'\t' read -r epic total done wip blocked; do
168
+ echo "| $epic | $total | $done | $wip | $blocked |"
169
+ done
170
+ } >> "$dashboard_view"
171
+ log_pass "$dashboard_view"
172
+
173
+ local release_view="$VIEWS_DIR/release-readiness.md"
174
+ write_header "$release_view" "Release Readiness"
175
+ {
176
+ echo "| Gate | Status |"
177
+ echo "|------|--------|"
178
+ blocked_count=$(ticket_count_by_status BLOCKED)
179
+ qa_count=$(ticket_count_by_status QA)
180
+ in_review_count=$(ticket_count_by_status IN_REVIEW)
181
+ missing_pr_count=$(find "$TICKETS_DIR" -name 'TICKET-*.json' -type f -print0 2>/dev/null |
182
+ while IFS= read -r -d '' file; do
183
+ if [ "$(jq -r '.status' "$file")" = "DONE" ] && [ "$(jq -r '.pr_url // empty' "$file")" = "" ]; then
184
+ echo 1
185
+ fi
186
+ done | wc -l | tr -d ' ')
187
+ echo "| Blocked tickets | $blocked_count |"
188
+ echo "| Tickets still in QA | $qa_count |"
189
+ echo "| Tickets still in review | $in_review_count |"
190
+ echo "| DONE tickets missing PR link | $missing_pr_count |"
191
+ echo ""
192
+ echo "## Latest Release Records"
193
+ echo ""
194
+ echo "| Version | Status | Created At | Released At |"
195
+ echo "|---------|--------|------------|-------------|"
196
+ find "$STATE_DIR/releases" -name '*.json' -type f -print0 2>/dev/null |
197
+ while IFS= read -r -d '' file; do
198
+ version=$(jq -r '.version // ""' "$file")
199
+ status=$(jq -r '.status // ""' "$file")
200
+ created_at=$(jq -r '.created_at // ""' "$file")
201
+ released_at=$(jq -r '.released_at // ""' "$file")
202
+ echo "| $version | $status | $created_at | $released_at |"
203
+ done
204
+ } >> "$release_view"
205
+ log_pass "$release_view"
206
+
207
+ log_pass "All views generated"
208
+ }
209
+
210
+ main "$@"