convoke-agents 2.4.0 → 3.0.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/CHANGELOG.md +16 -0
- package/INSTALLATION.md +109 -86
- package/README.md +220 -163
- package/UPDATE-GUIDE.md +63 -23
- package/_bmad/bme/_gyre/README.md +100 -0
- package/_bmad/bme/_gyre/agents/.gitkeep +0 -0
- package/_bmad/bme/_gyre/agents/model-curator.md +128 -0
- package/_bmad/bme/_gyre/agents/readiness-analyst.md +127 -0
- package/_bmad/bme/_gyre/agents/review-coach.md +130 -0
- package/_bmad/bme/_gyre/agents/stack-detective.md +125 -0
- package/_bmad/bme/_gyre/compass-routing-reference.md +168 -0
- package/_bmad/bme/_gyre/config.yaml +22 -0
- package/_bmad/bme/_gyre/contracts/.gitkeep +0 -0
- package/_bmad/bme/_gyre/contracts/gc1-stack-profile.md +152 -0
- package/_bmad/bme/_gyre/contracts/gc2-capabilities-manifest.md +189 -0
- package/_bmad/bme/_gyre/contracts/gc3-findings-report.md +197 -0
- package/_bmad/bme/_gyre/contracts/gc4-feedback-loop.md +209 -0
- package/_bmad/bme/_gyre/guides/ATLAS-USER-GUIDE.md +177 -0
- package/_bmad/bme/_gyre/guides/COACH-USER-GUIDE.md +172 -0
- package/_bmad/bme/_gyre/guides/LENS-USER-GUIDE.md +181 -0
- package/_bmad/bme/_gyre/guides/SCOUT-USER-GUIDE.md +158 -0
- package/_bmad/bme/_gyre/workflows/.gitkeep +0 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/steps/step-01-select-repos.md +55 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/steps/step-02-run-validation.md +78 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/steps/step-03-score-results.md +143 -0
- package/_bmad/bme/_gyre/workflows/accuracy-validation/workflow.md +41 -0
- package/_bmad/bme/_gyre/workflows/delta-report/steps/step-01-load-history.md +63 -0
- package/_bmad/bme/_gyre/workflows/delta-report/steps/step-02-compute-delta.md +72 -0
- package/_bmad/bme/_gyre/workflows/delta-report/steps/step-03-present-delta.md +143 -0
- package/_bmad/bme/_gyre/workflows/delta-report/workflow.md +34 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-01-initialize.md +68 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-02-detect-stack.md +49 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-03-generate-model.md +52 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-04-analyze-gaps.md +42 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/steps/step-05-review-findings.md +128 -0
- package/_bmad/bme/_gyre/workflows/full-analysis/workflow.md +39 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-01-load-manifest.md +70 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-02-observability-analysis.md +110 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-03-deployment-analysis.md +87 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-04-cross-domain-correlation.md +105 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/steps/step-05-present-findings.md +172 -0
- package/_bmad/bme/_gyre/workflows/gap-analysis/workflow.md +38 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-01-load-profile.md +74 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-02-generate-capabilities.md +116 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-03-web-enrichment.md +89 -0
- package/_bmad/bme/_gyre/workflows/model-generation/steps/step-04-write-manifest.md +122 -0
- package/_bmad/bme/_gyre/workflows/model-generation/workflow.md +40 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-01-load-context.md +86 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-02-walkthrough.md +116 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-03-apply-amendments.md +92 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-04-capture-feedback.md +107 -0
- package/_bmad/bme/_gyre/workflows/model-review/steps/step-05-summary.md +60 -0
- package/_bmad/bme/_gyre/workflows/model-review/workflow.md +41 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/steps/step-01-scan-filesystem.md +176 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/steps/step-02-classify-stack.md +111 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/steps/step-03-guard-questions.md +117 -0
- package/_bmad/bme/_gyre/workflows/stack-detection/workflow.md +42 -0
- package/_bmad/bme/_vortex/config.yaml +1 -1
- package/package.json +5 -2
- package/scripts/archive.js +304 -0
- package/scripts/convoke-doctor.js +146 -132
- package/scripts/docs-audit.js +21 -5
- package/scripts/install-gyre-agents.js +140 -0
- package/scripts/install-vortex-agents.js +0 -0
- package/scripts/update/lib/agent-registry.js +70 -0
- package/scripts/update/lib/refresh-installation.js +152 -30
- package/scripts/update/lib/validator.js +160 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
step: 2
|
|
3
|
+
workflow: stack-detection
|
|
4
|
+
title: Classify Stack
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Step 2: Classify Stack
|
|
8
|
+
|
|
9
|
+
Organize scan results into a structured stack classification. Identify ambiguities that require guard questions.
|
|
10
|
+
|
|
11
|
+
## MANDATORY EXECUTION RULES
|
|
12
|
+
|
|
13
|
+
- Classification is derived from Step 1 evidence — do NOT introduce new scans here
|
|
14
|
+
- If multiple stacks detected (monorepo), select primary and surface secondary as warning (FR1b)
|
|
15
|
+
- Identify ambiguities honestly — do not force classification where evidence is unclear
|
|
16
|
+
- Confidence levels must reflect actual evidence strength
|
|
17
|
+
|
|
18
|
+
## CLASSIFICATION PROCESS
|
|
19
|
+
|
|
20
|
+
### 1. Primary Stack Identification
|
|
21
|
+
|
|
22
|
+
From the scan results, determine the **primary stack archetype**. Common archetypes:
|
|
23
|
+
|
|
24
|
+
| Archetype | Indicators |
|
|
25
|
+
|-----------|------------|
|
|
26
|
+
| Node.js Web Service | package.json + Express/Fastify/Nest + REST endpoints |
|
|
27
|
+
| Node.js Frontend App | package.json + React/Vue/Angular/Next.js/Svelte |
|
|
28
|
+
| Go Microservice | go.mod + Gin/Echo/Chi + gRPC/REST + container |
|
|
29
|
+
| Python Data Pipeline | requirements.txt/pyproject.toml + pandas/spark/airflow |
|
|
30
|
+
| Python Web Service | requirements.txt + Django/Flask/FastAPI |
|
|
31
|
+
| JVM Enterprise | pom.xml/build.gradle + Spring Boot + container orchestration |
|
|
32
|
+
| Rust System Service | Cargo.toml + Actix/Axum/Tokio |
|
|
33
|
+
| Ruby Web App | Gemfile + Rails/Sinatra |
|
|
34
|
+
| .NET Service | *.csproj + ASP.NET + Azure indicators |
|
|
35
|
+
| Multi-language | Multiple primary manifests at different paths |
|
|
36
|
+
|
|
37
|
+
If the project doesn't fit a known archetype, classify it descriptively rather than forcing a fit.
|
|
38
|
+
|
|
39
|
+
### 2. Multi-Stack Detection (FR1b)
|
|
40
|
+
|
|
41
|
+
If multiple package manifests with deployment configs are found at different paths:
|
|
42
|
+
|
|
43
|
+
- This is a **monorepo or multi-service project**
|
|
44
|
+
- List each detected service root with its stack
|
|
45
|
+
- **Do NOT attempt implicit boundary detection** from directory naming
|
|
46
|
+
- Flag for user selection in Step 3 (or Story 1.5 monorepo handling)
|
|
47
|
+
|
|
48
|
+
### 3. Confidence Assessment
|
|
49
|
+
|
|
50
|
+
For each classification category, assign a confidence level:
|
|
51
|
+
|
|
52
|
+
| Level | Meaning | Criteria |
|
|
53
|
+
|-------|---------|----------|
|
|
54
|
+
| **high** | Strong evidence from multiple sources | ≥2 confirming indicators, no contradictions |
|
|
55
|
+
| **medium** | Evidence exists but could be interpreted differently | 1 strong indicator or multiple weak ones |
|
|
56
|
+
| **low** | Inferred from indirect evidence | No direct indicator, classification based on related patterns |
|
|
57
|
+
| **none** | No evidence found | Category left blank in profile |
|
|
58
|
+
|
|
59
|
+
### 4. Ambiguity Identification
|
|
60
|
+
|
|
61
|
+
Flag categories where guard questions would help:
|
|
62
|
+
|
|
63
|
+
- **Deployment model ambiguous:** Dockerfile exists but no orchestration detected — could be container-based, serverless-backed, or local-dev-only
|
|
64
|
+
- **Communication protocol ambiguous:** Both REST routes and gRPC protos detected — which is primary?
|
|
65
|
+
- **Cloud provider ambiguous:** Multiple provider SDKs imported, or IaC references multiple providers
|
|
66
|
+
- **Architecture intent unclear:** Evidence points multiple directions (e.g., monolith with microservice-style configs)
|
|
67
|
+
|
|
68
|
+
Record each ambiguity with: what was detected, why it's ambiguous, and what a guard question would clarify.
|
|
69
|
+
|
|
70
|
+
## CLASSIFICATION OUTPUT
|
|
71
|
+
|
|
72
|
+
Present the classification to the user:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
## Stack Classification
|
|
76
|
+
|
|
77
|
+
**Primary Archetype:** [e.g., Node.js Web Service on AWS with Kubernetes]
|
|
78
|
+
|
|
79
|
+
| Category | Classification | Confidence |
|
|
80
|
+
|----------|---------------|:----------:|
|
|
81
|
+
| Language/Framework | [value] | high/medium/low |
|
|
82
|
+
| Container | [value] | high/medium/low |
|
|
83
|
+
| Orchestration | [value or "not detected"] | high/medium/low/none |
|
|
84
|
+
| CI/CD | [value] | high/medium/low |
|
|
85
|
+
| Observability | [value or "not detected"] | high/medium/low/none |
|
|
86
|
+
| Cloud Provider | [value] | high/medium/low |
|
|
87
|
+
| Communication | [value] | high/medium/low |
|
|
88
|
+
|
|
89
|
+
**Ambiguities found:** [count]
|
|
90
|
+
[List each ambiguity briefly]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If secondary stacks were detected, include:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
⚠️ **Secondary stacks detected:**
|
|
97
|
+
- [path]: [stack description]
|
|
98
|
+
- [path]: [stack description]
|
|
99
|
+
|
|
100
|
+
Scout will analyze the primary stack. To analyze a secondary stack, select it explicitly.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## NEXT STEP
|
|
106
|
+
|
|
107
|
+
If ambiguities were identified → proceed to guard questions:
|
|
108
|
+
|
|
109
|
+
Load step: {project-root}/_bmad/bme/_gyre/workflows/stack-detection/steps/step-03-guard-questions.md
|
|
110
|
+
|
|
111
|
+
If classification is fully unambiguous (all categories high confidence, no contradictions) → skip guard questions and inform the user that detection is complete with high confidence. Proceed to GC1 contract writing (Story 1.4).
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
step: 3
|
|
3
|
+
workflow: stack-detection
|
|
4
|
+
title: Guard Questions
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Step 3: Guard Questions
|
|
8
|
+
|
|
9
|
+
Ask targeted questions to resolve ambiguities identified in Step 2. Guard questions confirm architecture intent — they don't repeat what detection already proved.
|
|
10
|
+
|
|
11
|
+
## MANDATORY EXECUTION RULES
|
|
12
|
+
|
|
13
|
+
- **Maximum 3 questions.** If more than 3 ambiguities exist, prioritize by impact on downstream model generation.
|
|
14
|
+
- **Skip entirely** if Step 2 classification has no ambiguities (all high confidence).
|
|
15
|
+
- Questions are **derived from detection**, not a fixed checklist. Every question must reference what was found and why it's ambiguous.
|
|
16
|
+
- User answers conversationally (FR7) — accept natural language, not just option numbers.
|
|
17
|
+
- If user corrects a previous answer, re-classify without re-scanning (FR8).
|
|
18
|
+
- Guard response processing takes <1 second (NFR3) — no additional file scanning on correction.
|
|
19
|
+
- Guard options must cover ≥95% of common architectures (NFR20).
|
|
20
|
+
|
|
21
|
+
## QUESTION GENERATION
|
|
22
|
+
|
|
23
|
+
For each ambiguity from Step 2, generate a question following this pattern:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
I found [evidence] which suggests [interpretation A], but [conflicting evidence or absence]
|
|
27
|
+
could also mean [interpretation B].
|
|
28
|
+
|
|
29
|
+
**Question:** [Specific question that resolves the ambiguity]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
a) [Interpretation A] — [what this means for the analysis]
|
|
33
|
+
b) [Interpretation B] — [what this means for the analysis]
|
|
34
|
+
c) Something else — [tell me more]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Common Guard Question Templates
|
|
38
|
+
|
|
39
|
+
**Deployment model (when Dockerfile exists but no orchestration):**
|
|
40
|
+
> I found a Dockerfile but no orchestration config (no k8s manifests, no ECS task definitions, no docker-compose for production).
|
|
41
|
+
>
|
|
42
|
+
> Is this container used for:
|
|
43
|
+
> a) Production deployment (container-based, orchestrated elsewhere)
|
|
44
|
+
> b) Local development only (production uses a different deployment model)
|
|
45
|
+
> c) Serverless with container image (e.g., AWS Lambda container, Cloud Run)
|
|
46
|
+
|
|
47
|
+
**Communication protocol (when multiple detected):**
|
|
48
|
+
> I found both REST endpoints (Express routes) and gRPC definitions (.proto files).
|
|
49
|
+
>
|
|
50
|
+
> What's the primary communication pattern:
|
|
51
|
+
> a) REST is the public API, gRPC is internal service-to-service
|
|
52
|
+
> b) gRPC is primary, REST is a gateway/proxy layer
|
|
53
|
+
> c) Both are primary — different clients use different protocols
|
|
54
|
+
|
|
55
|
+
**Cloud provider (when ambiguous):**
|
|
56
|
+
> I found Terraform files referencing both AWS and GCP providers.
|
|
57
|
+
>
|
|
58
|
+
> Is this:
|
|
59
|
+
> a) Primarily AWS with some GCP services
|
|
60
|
+
> b) Primarily GCP with some AWS services
|
|
61
|
+
> c) True multi-cloud deployment
|
|
62
|
+
|
|
63
|
+
## CONVERSATION FLOW
|
|
64
|
+
|
|
65
|
+
1. Present all guard questions at once (up to 3), numbered clearly
|
|
66
|
+
2. Accept answers in any order, any format (number, letter, natural language)
|
|
67
|
+
3. After each answer, acknowledge and update the classification
|
|
68
|
+
4. If user says something like "actually, I said X but it's really Y" — re-classify that category without re-scanning
|
|
69
|
+
5. When all questions are answered (or user says "that's correct" / "looks good"):
|
|
70
|
+
|
|
71
|
+
Present the **final classification** incorporating guard answers:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
## Final Stack Classification
|
|
75
|
+
|
|
76
|
+
**Archetype:** [updated archetype with guard refinements]
|
|
77
|
+
|
|
78
|
+
| Category | Classification | Confidence | Source |
|
|
79
|
+
|----------|---------------|:----------:|--------|
|
|
80
|
+
| Language/Framework | [value] | high | detection |
|
|
81
|
+
| Container | [value] | high | detection |
|
|
82
|
+
| Orchestration | [value] | high | detection + guard |
|
|
83
|
+
| CI/CD | [value] | high | detection |
|
|
84
|
+
| Observability | [value] | medium | detection |
|
|
85
|
+
| Cloud Provider | [value] | high | guard |
|
|
86
|
+
| Communication | [value] | high | guard |
|
|
87
|
+
|
|
88
|
+
Does this look correct? If so, I'll write the Stack Profile for Atlas to use.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## CORRECTION HANDLING (FR8)
|
|
92
|
+
|
|
93
|
+
If the user corrects a previously answered guard question or any detection result:
|
|
94
|
+
|
|
95
|
+
1. Accept the correction conversationally
|
|
96
|
+
2. Update the affected classification category
|
|
97
|
+
3. Check if the correction affects other categories (e.g., changing cloud provider might affect deployment model)
|
|
98
|
+
4. Re-present the updated classification
|
|
99
|
+
5. Do NOT re-scan the filesystem — classification update only
|
|
100
|
+
|
|
101
|
+
## COMPLETION
|
|
102
|
+
|
|
103
|
+
When the user confirms the classification is correct, the stack detection workflow is complete.
|
|
104
|
+
|
|
105
|
+
The confirmed classification is ready for GC1 contract writing (Story 1.4). Scout will write the Stack Profile to `.gyre/stack-profile.yaml`.
|
|
106
|
+
|
|
107
|
+
Present the compass routing options:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
**What's next?**
|
|
111
|
+
|
|
112
|
+
| Option | Description |
|
|
113
|
+
|--------|-------------|
|
|
114
|
+
| Write Stack Profile | Save classification as GC1 artifact for Atlas |
|
|
115
|
+
| Chat | Discuss the detection results with Scout |
|
|
116
|
+
| Menu | Return to Scout's main menu |
|
|
117
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
workflow: stack-detection
|
|
3
|
+
type: step-file
|
|
4
|
+
description: Detect and classify project technology stack through filesystem analysis
|
|
5
|
+
author: Scout (stack-detective)
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack Detection Workflow
|
|
10
|
+
|
|
11
|
+
This workflow guides Scout through scanning a project's filesystem to detect and classify its technology stack. The output is a Stack Profile that downstream agents use for contextual model generation.
|
|
12
|
+
|
|
13
|
+
## What is Stack Detection?
|
|
14
|
+
|
|
15
|
+
Stack detection identifies the technologies, frameworks, infrastructure, and tooling used in a project by examining its filesystem artifacts — package manifests, config files, IaC templates, CI pipelines, and dependency declarations. No code is executed; all detection is static.
|
|
16
|
+
|
|
17
|
+
## Workflow Structure
|
|
18
|
+
|
|
19
|
+
**Step-file architecture:**
|
|
20
|
+
- Just-in-time loading (each step loads only when needed)
|
|
21
|
+
- Sequential enforcement (must complete step N before step N+1)
|
|
22
|
+
- State tracking in frontmatter (progress preserved)
|
|
23
|
+
|
|
24
|
+
## Steps Overview
|
|
25
|
+
|
|
26
|
+
1. **Scan Filesystem** — Use Glob, Grep, and Read to discover technology indicators
|
|
27
|
+
2. **Classify Stack** — Organize findings into a structured classification with confidence levels
|
|
28
|
+
3. **Guard Questions** — Ask ≤3 targeted questions to resolve ambiguities (skip if unambiguous)
|
|
29
|
+
|
|
30
|
+
## Output
|
|
31
|
+
|
|
32
|
+
**Artifact:** Stack Profile at `.gyre/stack-profile.yaml` (GC1 contract)
|
|
33
|
+
|
|
34
|
+
**Privacy boundary:** Stack Profile contains technology categories only — NOT file contents, file paths, version numbers, dependency counts, dependency names, or secrets.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## INITIALIZATION
|
|
39
|
+
|
|
40
|
+
Load config from {project-root}/_bmad/bme/_gyre/config.yaml
|
|
41
|
+
|
|
42
|
+
Load step: {project-root}/_bmad/bme/_gyre/workflows/stack-detection/steps/step-01-scan-filesystem.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "convoke-agents",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Agent teams for complex systems, compatible with BMad Method",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"scripts/",
|
|
9
9
|
"_bmad/bme/_vortex/",
|
|
10
10
|
"_bmad/bme/_enhance/",
|
|
11
|
+
"_bmad/bme/_gyre/",
|
|
11
12
|
"INSTALLATION.md",
|
|
12
13
|
"UPDATE-GUIDE.md",
|
|
13
14
|
"CHANGELOG.md",
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
],
|
|
18
19
|
"bin": {
|
|
19
20
|
"convoke-install-vortex": "scripts/install-vortex-agents.js",
|
|
21
|
+
"convoke-install-gyre": "scripts/install-gyre-agents.js",
|
|
20
22
|
"convoke-install": "scripts/install-all-agents.js",
|
|
21
23
|
"convoke-update": "scripts/update/convoke-update.js",
|
|
22
24
|
"convoke-version": "scripts/update/convoke-version.js",
|
|
@@ -33,7 +35,8 @@
|
|
|
33
35
|
"test:all": "node --test tests/**/*.test.js",
|
|
34
36
|
"test:coverage": "c8 node --test tests/unit/*.test.js tests/integration/*.test.js tests/p0/*.test.js",
|
|
35
37
|
"lint": "eslint scripts/ index.js tests/",
|
|
36
|
-
"docs:audit": "node scripts/docs-audit.js"
|
|
38
|
+
"docs:audit": "node scripts/docs-audit.js",
|
|
39
|
+
"archive": "node scripts/archive.js"
|
|
37
40
|
},
|
|
38
41
|
"keywords": [
|
|
39
42
|
"convoke-agents",
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { findProjectRoot } = require('./update/lib/utils');
|
|
6
|
+
|
|
7
|
+
// --- Category registry from ADR ---
|
|
8
|
+
const VALID_CATEGORIES = [
|
|
9
|
+
'prd', 'epic', 'arch', 'adr', 'brief', 'report', 'spec', 'vision',
|
|
10
|
+
'hc', 'persona', 'experiment', 'learning', 'sprint', 'decision',
|
|
11
|
+
'research'
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const NAMING_PATTERN = /^[a-z][a-z0-9-]*\.(?:md|yaml)$/;
|
|
15
|
+
|
|
16
|
+
// Living documents that are exempt from the category prefix requirement
|
|
17
|
+
const EXEMPT_FILES = [
|
|
18
|
+
'architecture.md', 'architecture-gyre.md',
|
|
19
|
+
'prd.md', 'initiatives-backlog.md'
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// Directories to scan for superseded files (relative to _bmad-output/)
|
|
23
|
+
const SCAN_DIRS = ['planning-artifacts', 'vortex-artifacts', 'implementation-artifacts'];
|
|
24
|
+
|
|
25
|
+
// Directories/files to skip when scanning _bmad-output/ root
|
|
26
|
+
const SKIP_ROOT = ['.backups', '.logs', '_archive', ...SCAN_DIRS,
|
|
27
|
+
'brainstorming', 'design-artifacts', 'journey-examples',
|
|
28
|
+
'project-documentation', 'test-artifacts'];
|
|
29
|
+
|
|
30
|
+
function isValidCategory(cat) {
|
|
31
|
+
const base = cat.replace(/\d+$/, '');
|
|
32
|
+
return VALID_CATEGORIES.includes(base) || VALID_CATEGORIES.includes(cat);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DATED_PATTERN = /^(.+)-(\d{4}-\d{2}-\d{2})\.(md|yaml)$/;
|
|
36
|
+
const CATEGORIZED_PATTERN = /^([a-z]+\d*)-(.+)\.(md|yaml)$/;
|
|
37
|
+
|
|
38
|
+
// --- Helpers ---
|
|
39
|
+
|
|
40
|
+
function parseFilename(filename) {
|
|
41
|
+
const lower = filename.toLowerCase();
|
|
42
|
+
const dated = lower.match(DATED_PATTERN);
|
|
43
|
+
const categorized = lower.match(CATEGORIZED_PATTERN);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
filename,
|
|
47
|
+
isDated: !!dated,
|
|
48
|
+
date: dated ? dated[2] : null,
|
|
49
|
+
baseName: dated ? dated[1] : lower.replace(/\.(md|yaml)$/, ''),
|
|
50
|
+
category: categorized ? categorized[1] : null,
|
|
51
|
+
hasValidCategory: categorized ? isValidCategory(categorized[1]) : false,
|
|
52
|
+
isUppercase: filename !== lower,
|
|
53
|
+
matchesConvention: NAMING_PATTERN.test(filename) && categorized && isValidCategory(categorized[1])
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toLowerKebab(filename) {
|
|
58
|
+
return filename.toLowerCase();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function groupByKey(files) {
|
|
62
|
+
const groups = {};
|
|
63
|
+
for (const f of files) {
|
|
64
|
+
if (!f.isDated) continue;
|
|
65
|
+
const key = f.baseName;
|
|
66
|
+
if (!groups[key]) groups[key] = [];
|
|
67
|
+
groups[key].push(f);
|
|
68
|
+
}
|
|
69
|
+
return groups;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function appendToIndex(indexPath, entries) {
|
|
73
|
+
const date = new Date().toISOString().split('T')[0];
|
|
74
|
+
let content = '';
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(indexPath)) {
|
|
77
|
+
content = '# Archive Index\n\n_Traceability log for archived artifacts. Append-only._\n\n';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
content += `\n## Automated archive — ${date}\n\n`;
|
|
81
|
+
content += '| File | Original Location | Archive Date | Reason |\n';
|
|
82
|
+
content += '|------|-------------------|--------------|--------|\n';
|
|
83
|
+
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
content += `| ${entry.archivedAs || entry.filename} | ${entry.originalDir}/ | ${date} | ${entry.reason} |\n`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fs.appendFileSync(indexPath, content);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- Main ---
|
|
92
|
+
|
|
93
|
+
async function run() {
|
|
94
|
+
const args = process.argv.slice(2);
|
|
95
|
+
const apply = args.includes('--apply');
|
|
96
|
+
const rename = args.includes('--rename');
|
|
97
|
+
const help = args.includes('--help') || args.includes('-h');
|
|
98
|
+
|
|
99
|
+
if (help) {
|
|
100
|
+
console.log(`
|
|
101
|
+
Usage: node scripts/archive.js [options]
|
|
102
|
+
|
|
103
|
+
Options:
|
|
104
|
+
--apply Execute changes (default: dry-run)
|
|
105
|
+
--rename Include naming convention checks and fixes
|
|
106
|
+
--rename --apply Fix uppercase filenames and archive loose root files
|
|
107
|
+
--help Show this help
|
|
108
|
+
|
|
109
|
+
Dry-run by default — shows what would happen without changing anything.
|
|
110
|
+
`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const projectRoot = findProjectRoot();
|
|
115
|
+
if (!projectRoot) {
|
|
116
|
+
console.error('Error: Could not find project root (_bmad/ directory not found).');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const outputDir = path.join(projectRoot, '_bmad-output');
|
|
121
|
+
const archiveDir = path.join(outputDir, '_archive');
|
|
122
|
+
const indexPath = path.join(archiveDir, 'INDEX.md');
|
|
123
|
+
|
|
124
|
+
const actions = { archive: [], rename: [], warnings: [] };
|
|
125
|
+
|
|
126
|
+
// 1. Scan subdirectories for superseded dated files
|
|
127
|
+
for (const dir of SCAN_DIRS) {
|
|
128
|
+
const fullDir = path.join(outputDir, dir);
|
|
129
|
+
if (!fs.existsSync(fullDir)) continue;
|
|
130
|
+
|
|
131
|
+
const files = (await fs.readdir(fullDir))
|
|
132
|
+
.filter(f => !f.startsWith('.'))
|
|
133
|
+
.map(f => ({ ...parseFilename(f), dir }));
|
|
134
|
+
|
|
135
|
+
// Find superseded versions
|
|
136
|
+
const groups = groupByKey(files);
|
|
137
|
+
for (const [, group] of Object.entries(groups)) {
|
|
138
|
+
if (group.length <= 1) continue;
|
|
139
|
+
|
|
140
|
+
group.sort((a, b) => b.date.localeCompare(a.date));
|
|
141
|
+
const [newest, ...older] = group;
|
|
142
|
+
|
|
143
|
+
for (const old of older) {
|
|
144
|
+
actions.archive.push({
|
|
145
|
+
filename: old.filename,
|
|
146
|
+
originalDir: dir,
|
|
147
|
+
from: path.join(fullDir, old.filename),
|
|
148
|
+
to: path.join(archiveDir, 'superseded', old.filename),
|
|
149
|
+
reason: `Superseded by ${newest.filename}`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Flag naming convention violations in subdirs
|
|
155
|
+
if (rename) {
|
|
156
|
+
for (const f of files) {
|
|
157
|
+
if (f.matchesConvention) continue;
|
|
158
|
+
if (EXEMPT_FILES.includes(f.filename)) continue;
|
|
159
|
+
|
|
160
|
+
const issues = [];
|
|
161
|
+
if (f.isUppercase) {
|
|
162
|
+
issues.push('uppercase in filename');
|
|
163
|
+
const newName = toLowerKebab(f.filename);
|
|
164
|
+
actions.rename.push({
|
|
165
|
+
filename: f.filename,
|
|
166
|
+
newName,
|
|
167
|
+
dir,
|
|
168
|
+
from: path.join(fullDir, f.filename),
|
|
169
|
+
to: path.join(fullDir, newName)
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (!NAMING_PATTERN.test(f.filename) && !f.isUppercase) {
|
|
173
|
+
issues.push('not lowercase kebab-case');
|
|
174
|
+
}
|
|
175
|
+
if (!f.hasValidCategory) {
|
|
176
|
+
issues.push(`no valid category prefix (has: ${f.category || 'none'})`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (issues.length > 0) {
|
|
180
|
+
actions.warnings.push({
|
|
181
|
+
filename: f.filename,
|
|
182
|
+
dir,
|
|
183
|
+
issues
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. Scan _bmad-output/ root for loose files
|
|
191
|
+
const rootFiles = (await fs.readdir(outputDir))
|
|
192
|
+
.filter(f => {
|
|
193
|
+
if (f.startsWith('.')) return false;
|
|
194
|
+
if (SKIP_ROOT.includes(f)) return false;
|
|
195
|
+
const fullPath = path.join(outputDir, f);
|
|
196
|
+
return fs.statSync(fullPath).isFile();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (rootFiles.length > 0) {
|
|
200
|
+
for (const f of rootFiles) {
|
|
201
|
+
const archivedAs = toLowerKebab(f);
|
|
202
|
+
actions.archive.push({
|
|
203
|
+
filename: f,
|
|
204
|
+
archivedAs,
|
|
205
|
+
originalDir: '_bmad-output (root)',
|
|
206
|
+
from: path.join(outputDir, f),
|
|
207
|
+
to: path.join(archiveDir, 'exploratory', archivedAs),
|
|
208
|
+
reason: 'Loose file in _bmad-output root — archived as historical'
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// --- Report ---
|
|
214
|
+
|
|
215
|
+
const mode = apply ? 'APPLY' : 'DRY-RUN';
|
|
216
|
+
console.log(`\n=== Convoke Archive (${mode}) ===\n`);
|
|
217
|
+
|
|
218
|
+
const totalActions = actions.archive.length + actions.rename.length + actions.warnings.length;
|
|
219
|
+
if (totalActions === 0) {
|
|
220
|
+
console.log('Everything looks clean. No actions needed.');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Loose/superseded files to archive
|
|
225
|
+
if (actions.archive.length > 0) {
|
|
226
|
+
console.log(`📦 Files to archive: ${actions.archive.length}\n`);
|
|
227
|
+
for (const a of actions.archive) {
|
|
228
|
+
const dest = path.relative(outputDir, a.to);
|
|
229
|
+
console.log(` ${a.originalDir === '_bmad-output (root)' ? '' : a.originalDir + '/'}${a.filename}`);
|
|
230
|
+
console.log(` → ${dest}`);
|
|
231
|
+
console.log(` Reason: ${a.reason}\n`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Renames (uppercase → lowercase)
|
|
236
|
+
if (actions.rename.length > 0) {
|
|
237
|
+
console.log(`✏️ Files to rename: ${actions.rename.length}\n`);
|
|
238
|
+
for (const r of actions.rename) {
|
|
239
|
+
console.log(` ${r.dir}/${r.filename} → ${r.newName}`);
|
|
240
|
+
}
|
|
241
|
+
console.log('');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Convention warnings (non-actionable — just flagged)
|
|
245
|
+
if (actions.warnings.length > 0) {
|
|
246
|
+
console.log(`⚠️ Naming convention warnings: ${actions.warnings.length}\n`);
|
|
247
|
+
for (const w of actions.warnings) {
|
|
248
|
+
console.log(` ${w.dir}/${w.filename}`);
|
|
249
|
+
console.log(` Issues: ${w.issues.join(', ')}`);
|
|
250
|
+
}
|
|
251
|
+
console.log('');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Execute if --apply
|
|
255
|
+
if (apply) {
|
|
256
|
+
let executed = 0;
|
|
257
|
+
|
|
258
|
+
// Archive moves
|
|
259
|
+
if (actions.archive.length > 0) {
|
|
260
|
+
console.log('Executing archive moves...\n');
|
|
261
|
+
const indexEntries = [];
|
|
262
|
+
|
|
263
|
+
for (const a of actions.archive) {
|
|
264
|
+
await fs.ensureDir(path.dirname(a.to));
|
|
265
|
+
await fs.move(a.from, a.to, { overwrite: false });
|
|
266
|
+
indexEntries.push(a);
|
|
267
|
+
executed++;
|
|
268
|
+
console.log(` ✅ Archived: ${a.filename}${a.archivedAs && a.archivedAs !== a.filename ? ` → ${a.archivedAs}` : ''}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
appendToIndex(indexPath, indexEntries);
|
|
272
|
+
console.log(`\n📋 Updated _archive/INDEX.md with ${indexEntries.length} entries.`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Renames
|
|
276
|
+
if (rename && actions.rename.length > 0) {
|
|
277
|
+
console.log('\nExecuting renames...\n');
|
|
278
|
+
for (const r of actions.rename) {
|
|
279
|
+
await fs.move(r.from, r.to, { overwrite: false });
|
|
280
|
+
executed++;
|
|
281
|
+
console.log(` ✅ Renamed: ${r.filename} → ${r.newName}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log(`\nDone. ${executed} actions executed.`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Summary
|
|
289
|
+
console.log('\n--- Summary ---');
|
|
290
|
+
console.log(` Files to archive: ${actions.archive.length}`);
|
|
291
|
+
if (rename) {
|
|
292
|
+
console.log(` Files to rename: ${actions.rename.length}`);
|
|
293
|
+
console.log(` Convention warnings: ${actions.warnings.length}`);
|
|
294
|
+
}
|
|
295
|
+
if (!apply && (actions.archive.length > 0 || actions.rename.length > 0)) {
|
|
296
|
+
console.log('\n Run with --apply to execute changes.');
|
|
297
|
+
}
|
|
298
|
+
console.log('');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
run().catch(err => {
|
|
302
|
+
console.error('Archive error:', err.message);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|