specweave 0.17.16 → 0.17.19
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.md +405 -2495
- package/README.md +92 -2
- package/dist/locales/de/.gitkeep +0 -0
- package/dist/locales/de/cli.json +108 -0
- package/dist/locales/en/cli.json +287 -0
- package/dist/locales/en/errors.json +7 -0
- package/dist/locales/en/templates.json +6 -0
- package/dist/locales/es/.gitkeep +0 -0
- package/dist/locales/es/cli.json +41 -0
- package/dist/locales/fr/.gitkeep +0 -0
- package/dist/locales/fr/cli.json +108 -0
- package/dist/locales/ja/.gitkeep +0 -0
- package/dist/locales/ja/cli.json +108 -0
- package/dist/locales/ko/.gitkeep +0 -0
- package/dist/locales/ko/cli.json +108 -0
- package/dist/locales/pt/.gitkeep +0 -0
- package/dist/locales/pt/cli.json +108 -0
- package/dist/locales/ru/.gitkeep +0 -0
- package/dist/locales/ru/cli.json +269 -0
- package/dist/locales/zh/.gitkeep +0 -0
- package/dist/locales/zh/cli.json +108 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +188 -36
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +65 -6
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +54 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js +86 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +25 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +191 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +139 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js +389 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts +26 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +63 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +216 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.js +25 -13
- package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +83 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +466 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +43 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js +82 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts +5 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/task-sync.js +38 -2
- package/dist/plugins/specweave-github/lib/task-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +28 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +156 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +66 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +274 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +56 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +93 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -0
- package/dist/spec-parser.js +629 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +107 -3
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +48 -3
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/core/deduplication/command-deduplicator.d.ts +166 -0
- package/dist/src/core/deduplication/command-deduplicator.d.ts.map +1 -0
- package/dist/src/core/deduplication/command-deduplicator.js +254 -0
- package/dist/src/core/deduplication/command-deduplicator.js.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts +142 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js +453 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -0
- package/dist/src/core/living-docs/index.d.ts +10 -84
- package/dist/src/core/living-docs/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/index.js +10 -164
- package/dist/src/core/living-docs/index.js.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.d.ts +106 -0
- package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -0
- package/dist/src/core/living-docs/spec-distributor.js +823 -0
- package/dist/src/core/living-docs/spec-distributor.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +201 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/types.js +15 -0
- package/dist/src/core/living-docs/types.js.map +1 -0
- package/dist/src/core/logging/prompt-logger.d.ts +70 -0
- package/dist/src/core/logging/prompt-logger.d.ts.map +1 -0
- package/dist/src/core/logging/prompt-logger.js +247 -0
- package/dist/src/core/logging/prompt-logger.js.map +1 -0
- package/dist/src/core/status-line/status-line-manager.d.ts +15 -24
- package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
- package/dist/src/core/status-line/status-line-manager.js +33 -70
- package/dist/src/core/status-line/status-line-manager.js.map +1 -1
- package/dist/src/core/status-line/types.d.ts +19 -31
- package/dist/src/core/status-line/types.d.ts.map +1 -1
- package/dist/src/core/status-line/types.js +5 -9
- package/dist/src/core/status-line/types.js.map +1 -1
- package/dist/src/core/sync/conflict-resolver.d.ts +66 -0
- package/dist/src/core/sync/conflict-resolver.d.ts.map +1 -0
- package/dist/src/core/sync/conflict-resolver.js +108 -0
- package/dist/src/core/sync/conflict-resolver.js.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts +55 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.js +202 -0
- package/dist/src/core/sync/enhanced-content-builder.js.map +1 -0
- package/dist/src/core/sync/label-detector.d.ts +66 -0
- package/dist/src/core/sync/label-detector.d.ts.map +1 -0
- package/dist/src/core/sync/label-detector.js +211 -0
- package/dist/src/core/sync/label-detector.js.map +1 -0
- package/dist/src/core/sync/retry-logic.d.ts +64 -0
- package/dist/src/core/sync/retry-logic.d.ts.map +1 -0
- package/dist/src/core/sync/retry-logic.js +165 -0
- package/dist/src/core/sync/retry-logic.js.map +1 -0
- package/dist/src/core/sync/spec-content-sync.d.ts +88 -0
- package/dist/src/core/sync/spec-content-sync.d.ts.map +1 -0
- package/dist/src/core/sync/spec-content-sync.js +5 -0
- package/dist/src/core/sync/spec-content-sync.js.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts +100 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.js +424 -0
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -0
- package/dist/src/core/sync/status-cache.d.ts +91 -0
- package/dist/src/core/sync/status-cache.d.ts.map +1 -0
- package/dist/src/core/sync/status-cache.js +140 -0
- package/dist/src/core/sync/status-cache.js.map +1 -0
- package/dist/src/core/sync/status-mapper.d.ts +69 -0
- package/dist/src/core/sync/status-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/status-mapper.js +90 -0
- package/dist/src/core/sync/status-mapper.js.map +1 -0
- package/dist/src/core/sync/status-sync-engine.d.ts +162 -0
- package/dist/src/core/sync/status-sync-engine.d.ts.map +1 -0
- package/dist/src/core/sync/status-sync-engine.js +347 -0
- package/dist/src/core/sync/status-sync-engine.js.map +1 -0
- package/dist/src/core/sync/sync-event-logger.d.ts +99 -0
- package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -0
- package/dist/src/core/sync/sync-event-logger.js +103 -0
- package/dist/src/core/sync/sync-event-logger.js.map +1 -0
- package/dist/src/core/sync/types.d.ts +52 -0
- package/dist/src/core/sync/types.d.ts.map +1 -0
- package/dist/src/core/sync/types.js +5 -0
- package/dist/src/core/sync/types.js.map +1 -0
- package/dist/src/core/sync/workflow-detector.d.ts +95 -0
- package/dist/src/core/sync/workflow-detector.d.ts.map +1 -0
- package/dist/src/core/sync/workflow-detector.js +175 -0
- package/dist/src/core/sync/workflow-detector.js.map +1 -0
- package/dist/src/core/types/config.d.ts +51 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +47 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +4 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/dist/src/utils/github-url.d.ts +53 -0
- package/dist/src/utils/github-url.d.ts.map +1 -0
- package/dist/src/utils/github-url.js +90 -0
- package/dist/src/utils/github-url.js.map +1 -0
- package/dist/src/utils/spec-parser.d.ts +145 -0
- package/dist/src/utils/spec-parser.d.ts.map +1 -0
- package/dist/src/utils/spec-parser.js +640 -0
- package/dist/src/utils/spec-parser.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md +160 -13
- package/plugins/specweave/agents/pm/templates/increment-spec.md +158 -0
- package/plugins/specweave/agents/pm/templates/living-docs-spec.md +113 -0
- package/plugins/specweave/commands/specweave-done.md +163 -0
- package/plugins/specweave/commands/specweave.md +70 -405
- package/plugins/specweave/hooks/hooks.json +4 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/lib/update-status-line.sh +79 -111
- package/plugins/specweave/hooks/post-increment-planning.sh +133 -37
- package/plugins/specweave/hooks/pre-command-deduplication.sh +86 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.js +139 -34
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +234 -38
- package/plugins/specweave/skills/SKILLS-INDEX.md +4 -24
- package/plugins/specweave/skills/increment-planner/SKILL.md +94 -0
- package/plugins/specweave/skills/increment-work-router/SKILL.md +466 -0
- package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +1 -1
- package/plugins/specweave-ado/lib/ado-spec-content-sync.js +49 -5
- package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +72 -6
- package/plugins/specweave-ado/lib/ado-status-sync.js +80 -0
- package/plugins/specweave-ado/lib/ado-status-sync.ts +121 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +205 -0
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +248 -0
- package/plugins/specweave-github/commands/specweave-github-sync-spec.md +1 -1
- package/plugins/specweave-github/hooks/post-task-completion.sh +32 -0
- package/plugins/specweave-github/lib/duplicate-detector.js +370 -0
- package/plugins/specweave-github/lib/duplicate-detector.ts +525 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.ts +322 -0
- package/plugins/specweave-github/lib/epic-content-builder.js +227 -0
- package/plugins/specweave-github/lib/epic-content-builder.ts +317 -0
- package/plugins/specweave-github/lib/github-client.js +21 -10
- package/plugins/specweave-github/lib/github-client.ts +27 -16
- package/plugins/specweave-github/lib/github-epic-sync.js +488 -0
- package/plugins/specweave-github/lib/github-epic-sync.ts +715 -0
- package/plugins/specweave-github/lib/github-status-sync.js +71 -0
- package/plugins/specweave-github/lib/github-status-sync.ts +107 -0
- package/plugins/specweave-github/lib/task-sync.js +33 -2
- package/plugins/specweave-github/lib/task-sync.ts +44 -2
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +267 -0
- package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +1 -1
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.ts +196 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.js +304 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.ts +459 -0
- package/plugins/specweave-jira/lib/jira-status-sync.js +79 -0
- package/plugins/specweave-jira/lib/jira-status-sync.ts +139 -0
- package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
- package/src/templates/AGENTS.md.template +88 -1
- package/src/templates/CLAUDE.md.template +49 -0
- package/plugins/specweave/skills/increment-quality-judge/SKILL.md +0 -524
- package/plugins/specweave/skills/plugin-installer/SKILL.md +0 -353
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# update-status-line.sh
|
|
3
|
+
# update-status-line.sh (Simplified)
|
|
4
4
|
#
|
|
5
|
-
# Updates
|
|
6
|
-
#
|
|
5
|
+
# Updates status line cache with current increment progress.
|
|
6
|
+
# Shows: [increment-name] ████░░░░ X/Y tasks (Z open)
|
|
7
7
|
#
|
|
8
|
-
#
|
|
8
|
+
# Logic:
|
|
9
|
+
# 1. Scan all metadata.json for status=active/in-progress/planning
|
|
10
|
+
# 2. Take first (oldest) as current increment
|
|
11
|
+
# 3. Count all active/in-progress/planning as openCount
|
|
12
|
+
# 4. Parse current increment's tasks.md for progress
|
|
13
|
+
# 5. Write to cache
|
|
9
14
|
#
|
|
10
|
-
#
|
|
11
|
-
# {
|
|
12
|
-
# "incrementId": "0017-sync-architecture-fix",
|
|
13
|
-
# "incrementName": "sync-architecture-fix",
|
|
14
|
-
# "totalTasks": 30,
|
|
15
|
-
# "completedTasks": 15,
|
|
16
|
-
# "percentage": 50,
|
|
17
|
-
# "currentTask": {
|
|
18
|
-
# "id": "T-016",
|
|
19
|
-
# "title": "Update documentation"
|
|
20
|
-
# },
|
|
21
|
-
# "lastUpdate": "2025-11-10T15:30:00Z",
|
|
22
|
-
# "lastModified": 1699632600
|
|
23
|
-
# }
|
|
15
|
+
# Performance: 50-100ms (runs async, user doesn't wait)
|
|
24
16
|
|
|
25
17
|
set -euo pipefail
|
|
26
18
|
|
|
@@ -39,126 +31,102 @@ find_project_root() {
|
|
|
39
31
|
|
|
40
32
|
PROJECT_ROOT=$(find_project_root)
|
|
41
33
|
CACHE_FILE="$PROJECT_ROOT/.specweave/state/status-line.json"
|
|
42
|
-
|
|
34
|
+
INCREMENTS_DIR="$PROJECT_ROOT/.specweave/increments"
|
|
35
|
+
TMP_FILE="$PROJECT_ROOT/.specweave/state/.status-line-tmp.txt"
|
|
43
36
|
|
|
44
37
|
# Ensure state directory exists
|
|
45
38
|
mkdir -p "$PROJECT_ROOT/.specweave/state"
|
|
46
39
|
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
echo '{}' > "$CACHE_FILE"
|
|
51
|
-
exit 0
|
|
52
|
-
fi
|
|
40
|
+
# Step 1: Find all open increments (active/in-progress/planning)
|
|
41
|
+
# Write to temp file: "timestamp increment_id"
|
|
42
|
+
> "$TMP_FILE"
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if [[ -
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
if [[ -d "$INCREMENTS_DIR" ]]; then
|
|
45
|
+
for metadata in "$INCREMENTS_DIR"/*/metadata.json; do
|
|
46
|
+
if [[ -f "$metadata" ]]; then
|
|
47
|
+
status=$(jq -r '.status // ""' "$metadata" 2>/dev/null || echo "")
|
|
48
|
+
|
|
49
|
+
# Check if increment is open (active, in-progress, or planning)
|
|
50
|
+
if [[ "$status" == "active" ]] || [[ "$status" == "in-progress" ]] || [[ "$status" == "planning" ]]; then
|
|
51
|
+
increment_id=$(basename "$(dirname "$metadata")")
|
|
52
|
+
created=$(jq -r '.created // ""' "$metadata" 2>/dev/null || echo "1970-01-01T00:00:00Z")
|
|
53
|
+
|
|
54
|
+
# Write to temp file
|
|
55
|
+
echo "$created $increment_id" >> "$TMP_FILE"
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
done
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
# Step 2: Count open increments
|
|
62
|
+
OPEN_COUNT=$(wc -l < "$TMP_FILE" | tr -d ' ')
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
if [[ $OPEN_COUNT -eq 0 ]]; then
|
|
65
|
+
# No open increments
|
|
66
|
+
jq -n '{
|
|
67
|
+
current: null,
|
|
68
|
+
openCount: 0,
|
|
69
|
+
lastUpdate: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
70
|
+
}' > "$CACHE_FILE"
|
|
71
|
+
rm -f "$TMP_FILE"
|
|
66
72
|
exit 0
|
|
67
73
|
fi
|
|
68
74
|
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
# macOS
|
|
72
|
-
MTIME=$(stat -f %m "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
73
|
-
else
|
|
74
|
-
# Linux
|
|
75
|
-
MTIME=$(stat -c %Y "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
76
|
-
fi
|
|
75
|
+
# Step 3: Sort by timestamp (oldest first) and take first
|
|
76
|
+
CURRENT_INCREMENT=$(sort "$TMP_FILE" | head -1 | awk '{print $2}')
|
|
77
77
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
TOTAL_TASKS=$(grep -cE '^##+ T-' "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
78
|
+
# Clean up temp file
|
|
79
|
+
rm -f "$TMP_FILE"
|
|
81
80
|
|
|
82
|
-
#
|
|
83
|
-
|
|
81
|
+
# Step 4: Parse current increment's tasks.md for progress
|
|
82
|
+
TASKS_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/tasks.md"
|
|
83
|
+
TOTAL_TASKS=0
|
|
84
|
+
COMPLETED_TASKS=0
|
|
85
|
+
PERCENTAGE=0
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
COMPLETED_TASKS_INLINE=$(grep -c 'Status\*\*: \[x\]' "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
87
|
+
if [[ -f "$TASKS_FILE" ]]; then
|
|
88
|
+
# Count total tasks (## T- or ### T- headings)
|
|
89
|
+
TOTAL_TASKS=$(grep -cE '^##+ T-' "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
90
|
+
TOTAL_TASKS=$(echo "$TOTAL_TASKS" | tr -d '\n\r ' || echo 0)
|
|
90
91
|
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
# Count completed tasks (both checkbox formats)
|
|
93
|
+
# Format 1: [x] at line start
|
|
94
|
+
COMPLETED_STANDARD=$(grep -c '^\[x\]' "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
95
|
+
COMPLETED_STANDARD=$(echo "$COMPLETED_STANDARD" | tr -d '\n\r ' || echo 0)
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
# Format 2: **Status**: [x] inline
|
|
98
|
+
COMPLETED_INLINE=$(grep -c '\*\*Status\*\*: \[x\]' "$TASKS_FILE" 2>/dev/null || echo 0)
|
|
99
|
+
COMPLETED_INLINE=$(echo "$COMPLETED_INLINE" | tr -d '\n\r ' || echo 0)
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
if [[ "$TOTAL_TASKS" -gt 0 ]]; then
|
|
99
|
-
PERCENTAGE=$(( COMPLETED_TASKS * 100 / TOTAL_TASKS ))
|
|
100
|
-
else
|
|
101
|
-
PERCENTAGE=0
|
|
102
|
-
fi
|
|
101
|
+
COMPLETED_TASKS=$((COMPLETED_STANDARD + COMPLETED_INLINE))
|
|
103
102
|
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
CURRENT_TASK_LINE=$(grep -B1 '^\[ \]' "$TASKS_FILE" 2>/dev/null | grep -E '^##+ T-' | head -1 || echo "")
|
|
108
|
-
|
|
109
|
-
# If not found, try inline format (**Status**: [ ])
|
|
110
|
-
if [[ -z "$CURRENT_TASK_LINE" ]]; then
|
|
111
|
-
# Find line with **Status**: [ ], then look backward for task heading
|
|
112
|
-
TASK_LINE_NUM=$(grep -n '\*\*Status\*\*: \[ \]' "$TASKS_FILE" 2>/dev/null | head -1 | cut -d: -f1 || echo "")
|
|
113
|
-
if [[ -n "$TASK_LINE_NUM" ]]; then
|
|
114
|
-
# Get lines before the status line and find the task heading
|
|
115
|
-
CURRENT_TASK_LINE=$(head -n "$TASK_LINE_NUM" "$TASKS_FILE" | grep -E '^##+ T-' | tail -1 || echo "")
|
|
103
|
+
# Calculate percentage
|
|
104
|
+
if [[ $TOTAL_TASKS -gt 0 ]]; then
|
|
105
|
+
PERCENTAGE=$((COMPLETED_TASKS * 100 / TOTAL_TASKS))
|
|
116
106
|
fi
|
|
117
107
|
fi
|
|
118
|
-
CURRENT_TASK_ID=""
|
|
119
|
-
CURRENT_TASK_TITLE=""
|
|
120
|
-
|
|
121
|
-
if [[ -n "$CURRENT_TASK_LINE" ]]; then
|
|
122
|
-
# Extract task ID (T-NNN)
|
|
123
|
-
CURRENT_TASK_ID=$(echo "$CURRENT_TASK_LINE" | grep -o 'T-[0-9][0-9]*' || echo "")
|
|
124
|
-
|
|
125
|
-
# Extract task title (after "## T-NNN: ")
|
|
126
|
-
# Use parameter expansion to remove prefix
|
|
127
|
-
TEMP="${CURRENT_TASK_LINE#*: }"
|
|
128
|
-
CURRENT_TASK_TITLE=$(echo "$TEMP" | head -c 50)
|
|
129
|
-
fi
|
|
130
108
|
|
|
131
|
-
# Extract increment name (remove
|
|
132
|
-
INCREMENT_NAME=$(echo "$
|
|
133
|
-
|
|
134
|
-
# Build current task JSON
|
|
135
|
-
if [[ -n "$CURRENT_TASK_ID" ]]; then
|
|
136
|
-
CURRENT_TASK_JSON=$(jq -n \
|
|
137
|
-
--arg id "$CURRENT_TASK_ID" \
|
|
138
|
-
--arg title "$CURRENT_TASK_TITLE" \
|
|
139
|
-
'{id: $id, title: $title}')
|
|
140
|
-
else
|
|
141
|
-
CURRENT_TASK_JSON="null"
|
|
142
|
-
fi
|
|
109
|
+
# Step 5: Extract increment name (remove 4-digit prefix)
|
|
110
|
+
INCREMENT_NAME=$(echo "$CURRENT_INCREMENT" | sed 's/^[0-9]\{4\}-//')
|
|
143
111
|
|
|
144
|
-
# Write cache
|
|
112
|
+
# Step 6: Write cache
|
|
145
113
|
jq -n \
|
|
146
|
-
--arg id "$
|
|
114
|
+
--arg id "$CURRENT_INCREMENT" \
|
|
147
115
|
--arg name "$INCREMENT_NAME" \
|
|
148
|
-
--argjson total "$TOTAL_TASKS" \
|
|
149
116
|
--argjson completed "$COMPLETED_TASKS" \
|
|
117
|
+
--argjson total "$TOTAL_TASKS" \
|
|
150
118
|
--argjson percentage "$PERCENTAGE" \
|
|
151
|
-
--argjson
|
|
152
|
-
--argjson mtime "$MTIME" \
|
|
119
|
+
--argjson openCount "$OPEN_COUNT" \
|
|
153
120
|
'{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
121
|
+
current: {
|
|
122
|
+
id: $id,
|
|
123
|
+
name: $name,
|
|
124
|
+
completed: $completed,
|
|
125
|
+
total: $total,
|
|
126
|
+
percentage: $percentage
|
|
127
|
+
},
|
|
128
|
+
openCount: $openCount,
|
|
129
|
+
lastUpdate: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
162
130
|
}' > "$CACHE_FILE"
|
|
163
131
|
|
|
164
132
|
exit 0
|
|
@@ -396,15 +396,31 @@ create_github_issue() {
|
|
|
396
396
|
return 1
|
|
397
397
|
fi
|
|
398
398
|
|
|
399
|
-
# Extract
|
|
400
|
-
local
|
|
399
|
+
# Extract creation date from metadata.json and format as FS-YY-MM-DD
|
|
400
|
+
local issue_prefix="FS-UNKNOWN"
|
|
401
|
+
if [ -f "$metadata_file" ]; then
|
|
402
|
+
# Extract created date (format: "2025-11-12T12:46:00Z")
|
|
403
|
+
local created_date=$(cat "$metadata_file" 2>/dev/null | grep -o '"created"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
|
|
404
|
+
if [ -n "$created_date" ]; then
|
|
405
|
+
# Extract YY-MM-DD from date (e.g., "2025-11-12" -> "25-11-12")
|
|
406
|
+
local year=$(echo "$created_date" | cut -d'-' -f1 | cut -c3-4) # "2025" -> "25"
|
|
407
|
+
local month=$(echo "$created_date" | cut -d'-' -f2) # "11"
|
|
408
|
+
local day=$(echo "$created_date" | cut -d'-' -f3 | cut -d'T' -f1) # "12T..." -> "12"
|
|
409
|
+
issue_prefix="FS-${year}-${month}-${day}"
|
|
410
|
+
log_debug "Using date-based prefix from metadata: $issue_prefix"
|
|
411
|
+
else
|
|
412
|
+
log_debug "No created date in metadata, using fallback"
|
|
413
|
+
fi
|
|
414
|
+
else
|
|
415
|
+
log_debug "No metadata.json found, using fallback prefix"
|
|
416
|
+
fi
|
|
401
417
|
|
|
402
418
|
log_debug "Creating issue for repo: $repo"
|
|
403
|
-
log_debug "Title: [
|
|
419
|
+
log_debug "Title: [$issue_prefix] $title"
|
|
404
420
|
|
|
405
421
|
# Generate issue body
|
|
406
422
|
local issue_body=$(cat <<EOF
|
|
407
|
-
# [
|
|
423
|
+
# [$issue_prefix] $title
|
|
408
424
|
|
|
409
425
|
**Status**: Planning → Implementation
|
|
410
426
|
**Priority**: P1
|
|
@@ -436,43 +452,65 @@ EOF
|
|
|
436
452
|
local temp_body=$(mktemp)
|
|
437
453
|
echo "$issue_body" > "$temp_body"
|
|
438
454
|
|
|
439
|
-
# Create GitHub issue
|
|
440
|
-
log_debug "
|
|
455
|
+
# Create GitHub issue with FULL DUPLICATE PROTECTION
|
|
456
|
+
log_debug "Creating issue with DuplicateDetector (global protection)..."
|
|
441
457
|
|
|
442
|
-
|
|
458
|
+
# Call Node.js wrapper script with DuplicateDetector
|
|
459
|
+
local node_output=$(node scripts/create-github-issue-with-protection.js \
|
|
460
|
+
--title "[$issue_prefix] $title" \
|
|
461
|
+
--body "$issue_body" \
|
|
462
|
+
--pattern "[$issue_prefix]" \
|
|
463
|
+
--labels "specweave,increment" \
|
|
443
464
|
--repo "$repo" \
|
|
444
|
-
--title "[INC-$inc_number] $title" \
|
|
445
|
-
--body-file "$temp_body" \
|
|
446
|
-
--label "specweave,increment" \
|
|
447
465
|
2>&1)
|
|
448
466
|
|
|
449
|
-
local
|
|
467
|
+
local node_status=$?
|
|
450
468
|
|
|
451
469
|
# Clean up temp file
|
|
452
470
|
rm -f "$temp_body"
|
|
453
471
|
|
|
454
|
-
if [ $
|
|
455
|
-
log_error "
|
|
472
|
+
if [ $node_status -ne 0 ]; then
|
|
473
|
+
log_error "DuplicateDetector failed: $node_output"
|
|
456
474
|
return 1
|
|
457
475
|
fi
|
|
458
476
|
|
|
459
|
-
#
|
|
460
|
-
local issue_number
|
|
461
|
-
local issue_url
|
|
477
|
+
# Parse JSON output using jq (if available) or fallback to grep
|
|
478
|
+
local issue_number=""
|
|
479
|
+
local issue_url=""
|
|
480
|
+
local duplicates_found=0
|
|
481
|
+
local duplicates_closed=0
|
|
482
|
+
local was_reused="false"
|
|
483
|
+
|
|
484
|
+
if command -v jq >/dev/null 2>&1; then
|
|
485
|
+
issue_number=$(echo "$node_output" | jq -r '.issue.number')
|
|
486
|
+
issue_url=$(echo "$node_output" | jq -r '.issue.url')
|
|
487
|
+
duplicates_found=$(echo "$node_output" | jq -r '.duplicatesFound // 0')
|
|
488
|
+
duplicates_closed=$(echo "$node_output" | jq -r '.duplicatesClosed // 0')
|
|
489
|
+
was_reused=$(echo "$node_output" | jq -r '.wasReused // false')
|
|
490
|
+
else
|
|
491
|
+
# Fallback: grep-based parsing
|
|
492
|
+
issue_number=$(echo "$node_output" | grep -o '"number"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*')
|
|
493
|
+
issue_url=$(echo "$node_output" | grep -o '"url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
|
|
494
|
+
fi
|
|
462
495
|
|
|
463
496
|
if [ -z "$issue_number" ]; then
|
|
464
|
-
log_error "Could not extract issue number from
|
|
497
|
+
log_error "Could not extract issue number from DuplicateDetector output"
|
|
498
|
+
log_debug "Output was: $node_output"
|
|
465
499
|
return 1
|
|
466
500
|
fi
|
|
467
501
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
502
|
+
# Log results with duplicate detection info
|
|
503
|
+
if [ "$was_reused" = "true" ]; then
|
|
504
|
+
log_info " ♻️ Using existing issue #$issue_number (duplicate prevention)"
|
|
505
|
+
else
|
|
506
|
+
log_info " 📝 Issue #$issue_number created"
|
|
471
507
|
fi
|
|
472
|
-
|
|
473
|
-
log_info " 📝 Issue #$issue_number created"
|
|
474
508
|
log_info " 🔗 $issue_url"
|
|
475
509
|
|
|
510
|
+
if [ "$duplicates_found" -gt 0 ]; then
|
|
511
|
+
log_info " 🛡️ Duplicates detected: $duplicates_found (auto-closed: $duplicates_closed)"
|
|
512
|
+
fi
|
|
513
|
+
|
|
476
514
|
# Create or update metadata.json
|
|
477
515
|
local current_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
478
516
|
|
|
@@ -658,21 +696,55 @@ EOF
|
|
|
658
696
|
if [ "$auto_create" = "true" ]; then
|
|
659
697
|
log_info " 📦 Auto-create enabled, checking for GitHub CLI..."
|
|
660
698
|
|
|
661
|
-
#
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
699
|
+
# ============================================================================
|
|
700
|
+
# DUPLICATE DETECTION (v0.14.1+)
|
|
701
|
+
# ============================================================================
|
|
702
|
+
# Check if GitHub issue already exists in metadata.json
|
|
703
|
+
# If exists, skip creation (idempotent operation)
|
|
704
|
+
|
|
705
|
+
local metadata_file="$increment_dir/metadata.json"
|
|
706
|
+
local existing_issue=""
|
|
707
|
+
|
|
708
|
+
if [ -f "$metadata_file" ]; then
|
|
709
|
+
# Extract existing GitHub issue number from metadata
|
|
710
|
+
existing_issue=$(cat "$metadata_file" 2>/dev/null | \
|
|
711
|
+
grep -o '"github"[[:space:]]*:[[:space:]]*{[^}]*"issue"[[:space:]]*:[[:space:]]*[0-9]*' | \
|
|
712
|
+
grep -o '[0-9]*$')
|
|
713
|
+
|
|
714
|
+
if [ -n "$existing_issue" ]; then
|
|
715
|
+
log_info " ✅ GitHub issue already exists: #$existing_issue"
|
|
716
|
+
log_info " ⏭️ Skipping creation (idempotent)"
|
|
717
|
+
log_debug "Metadata already contains github.issue = $existing_issue"
|
|
718
|
+
|
|
719
|
+
# Extract URL if available
|
|
720
|
+
local existing_url=$(cat "$metadata_file" 2>/dev/null | \
|
|
721
|
+
grep -o '"url"[[:space:]]*:[[:space:]]*"[^"]*"' | \
|
|
722
|
+
sed 's/.*"\([^"]*\)".*/\1/')
|
|
723
|
+
|
|
724
|
+
if [ -n "$existing_url" ]; then
|
|
725
|
+
log_info " 🔗 $existing_url"
|
|
726
|
+
fi
|
|
727
|
+
fi
|
|
728
|
+
fi
|
|
669
729
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
730
|
+
# Only create if no existing issue found
|
|
731
|
+
if [ -z "$existing_issue" ]; then
|
|
732
|
+
# Check if gh CLI is available
|
|
733
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
734
|
+
log_info " ⚠️ GitHub CLI (gh) not found, skipping issue creation"
|
|
735
|
+
log_debug "Install: https://cli.github.com/"
|
|
673
736
|
else
|
|
674
|
-
log_info "
|
|
675
|
-
|
|
737
|
+
log_info " ✓ GitHub CLI found"
|
|
738
|
+
log_info ""
|
|
739
|
+
log_info "🚀 Creating GitHub issue for $increment_id..."
|
|
740
|
+
|
|
741
|
+
# Create issue (non-blocking)
|
|
742
|
+
if create_github_issue "$increment_id" "$increment_dir"; then
|
|
743
|
+
log_info " ✅ GitHub issue created successfully"
|
|
744
|
+
else
|
|
745
|
+
log_info " ⚠️ GitHub issue creation failed (non-blocking)"
|
|
746
|
+
log_debug "Issue creation failed, but continuing execution"
|
|
747
|
+
fi
|
|
676
748
|
fi
|
|
677
749
|
fi
|
|
678
750
|
else
|
|
@@ -705,14 +777,38 @@ EOF
|
|
|
705
777
|
fi
|
|
706
778
|
fi
|
|
707
779
|
|
|
708
|
-
#
|
|
780
|
+
# Read testing config from .specweave/config.json (NEW - v0.18.0+)
|
|
781
|
+
local test_mode="TDD"
|
|
782
|
+
local coverage_target=80
|
|
783
|
+
|
|
784
|
+
if [ -f "$CONFIG_FILE" ] && command -v jq >/dev/null 2>&1; then
|
|
785
|
+
test_mode=$(jq -r '.testing.defaultTestMode // "TDD"' "$CONFIG_FILE" 2>/dev/null || echo "TDD")
|
|
786
|
+
coverage_target=$(jq -r '.testing.defaultCoverageTarget // 80' "$CONFIG_FILE" 2>/dev/null || echo "80")
|
|
787
|
+
fi
|
|
788
|
+
|
|
789
|
+
# Check for overrides in spec.md frontmatter
|
|
790
|
+
if [ -f "$spec_file" ]; then
|
|
791
|
+
local spec_test_mode=$(awk '/^---$/,/^---$/ {if (/^test_mode:/) {sub(/^test_mode:[[:space:]]*"?/, ""); sub(/"?[[:space:]]*$/, ""); print; exit}}' "$spec_file" 2>/dev/null)
|
|
792
|
+
local spec_coverage=$(awk '/^---$/,/^---$/ {if (/^coverage_target:/) {sub(/^coverage_target:[[:space:]]*/, ""); sub(/[[:space:]]*$/, ""); print; exit}}' "$spec_file" 2>/dev/null)
|
|
793
|
+
|
|
794
|
+
if [ -n "$spec_test_mode" ]; then
|
|
795
|
+
test_mode="$spec_test_mode"
|
|
796
|
+
fi
|
|
797
|
+
if [ -n "$spec_coverage" ]; then
|
|
798
|
+
coverage_target="$spec_coverage"
|
|
799
|
+
fi
|
|
800
|
+
fi
|
|
801
|
+
|
|
802
|
+
# Create minimal metadata.json with testing config
|
|
709
803
|
cat > "$metadata_file" <<EOF_MINIMAL
|
|
710
804
|
{
|
|
711
805
|
"id": "$increment_id",
|
|
712
806
|
"status": "active",
|
|
713
807
|
"type": "$increment_type",
|
|
714
808
|
"created": "$current_timestamp",
|
|
715
|
-
"lastActivity": "$current_timestamp"
|
|
809
|
+
"lastActivity": "$current_timestamp",
|
|
810
|
+
"testMode": "$test_mode",
|
|
811
|
+
"coverageTarget": $coverage_target
|
|
716
812
|
}
|
|
717
813
|
EOF_MINIMAL
|
|
718
814
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# SpecWeave Pre-Command Deduplication Hook
|
|
4
|
+
# Fires BEFORE any command executes (UserPromptSubmit hook)
|
|
5
|
+
# Purpose: Prevent duplicate command invocations within configurable time window
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# Read input JSON from stdin
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
# Extract prompt from JSON
|
|
13
|
+
PROMPT=$(echo "$INPUT" | node -e "
|
|
14
|
+
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
|
|
15
|
+
console.log(input.prompt || '');
|
|
16
|
+
")
|
|
17
|
+
|
|
18
|
+
# ==============================================================================
|
|
19
|
+
# DEDUPLICATION CHECK: Block duplicate commands within 1 second
|
|
20
|
+
# ==============================================================================
|
|
21
|
+
|
|
22
|
+
# Extract command name from prompt (if slash command)
|
|
23
|
+
COMMAND=$(echo "$PROMPT" | grep -oE "^/[a-z0-9:-]+" | head -1 || echo "")
|
|
24
|
+
|
|
25
|
+
if [[ -n "$COMMAND" ]]; then
|
|
26
|
+
# Check deduplication using TypeScript module
|
|
27
|
+
if command -v node >/dev/null 2>&1 && [[ -f "dist/src/core/deduplication/command-deduplicator.js" ]]; then
|
|
28
|
+
# Run deduplication check
|
|
29
|
+
DEDUP_RESULT=$(node -e "
|
|
30
|
+
(async () => {
|
|
31
|
+
try {
|
|
32
|
+
const { CommandDeduplicator } = require('./dist/src/core/deduplication/command-deduplicator.js');
|
|
33
|
+
const dedup = new CommandDeduplicator({ debug: false });
|
|
34
|
+
|
|
35
|
+
// Parse command and args
|
|
36
|
+
const fullCommand = '${COMMAND}';
|
|
37
|
+
const args = '${PROMPT}'.replace(fullCommand, '').trim().split(/\\s+/).filter(Boolean);
|
|
38
|
+
|
|
39
|
+
// Check for duplicate
|
|
40
|
+
const isDuplicate = await dedup.checkDuplicate(fullCommand, args);
|
|
41
|
+
|
|
42
|
+
if (isDuplicate) {
|
|
43
|
+
const stats = dedup.getStats();
|
|
44
|
+
console.log('DUPLICATE');
|
|
45
|
+
console.log(JSON.stringify(stats));
|
|
46
|
+
} else {
|
|
47
|
+
// Record invocation
|
|
48
|
+
await dedup.recordInvocation(fullCommand, args);
|
|
49
|
+
console.log('OK');
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error('Error in deduplication:', e.message);
|
|
53
|
+
console.log('OK'); // Fail-open (don't block on errors)
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
56
|
+
" 2>/dev/null || echo "OK")
|
|
57
|
+
|
|
58
|
+
# Parse result
|
|
59
|
+
STATUS=$(echo "$DEDUP_RESULT" | head -1)
|
|
60
|
+
|
|
61
|
+
if [[ "$STATUS" == "DUPLICATE" ]]; then
|
|
62
|
+
# Get stats
|
|
63
|
+
STATS=$(echo "$DEDUP_RESULT" | tail -1)
|
|
64
|
+
|
|
65
|
+
cat <<EOF
|
|
66
|
+
{
|
|
67
|
+
"decision": "block",
|
|
68
|
+
"reason": "🚫 DUPLICATE COMMAND DETECTED\\n\\nCommand: \`$COMMAND\`\\nTime window: 1 second\\n\\nThis command was just executed! To prevent unintended duplicates, this invocation has been blocked.\\n\\n💡 If you meant to run this command again:\\n 1. Wait 1 second\\n 2. Run the command again\\n\\n📊 Deduplication Stats:\\n$STATS\\n\\n🔧 To adjust the time window, edit \`.specweave/config.json\`:\\n\`\`\`json\\n{\\n \\\"deduplication\\\": {\\n \\\"windowMs\\\": 2000 // Increase to 2 seconds\\n }\\n}\\n\`\`\`"
|
|
69
|
+
}
|
|
70
|
+
EOF
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# ==============================================================================
|
|
77
|
+
# PASS THROUGH: No duplicate detected, proceed with command
|
|
78
|
+
# ==============================================================================
|
|
79
|
+
|
|
80
|
+
cat <<EOF
|
|
81
|
+
{
|
|
82
|
+
"decision": "approve"
|
|
83
|
+
}
|
|
84
|
+
EOF
|
|
85
|
+
|
|
86
|
+
exit 0
|