convoke-agents 3.0.4 → 3.2.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.
- package/CHANGELOG.md +60 -0
- package/README.md +14 -13
- package/_bmad/bme/_artifacts/config.yaml +15 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
- package/_bmad/bme/_gyre/guides/GYRE-TEAM-GUIDE.md +506 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
- package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
- package/_bmad/bme/_team-factory/config.yaml +13 -0
- package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
- package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
- package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
- package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
- package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
- package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
- package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
- package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
- package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
- package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
- package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
- package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
- package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
- package/_bmad/bme/_team-factory/module-help.csv +3 -0
- package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
- package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
- package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
- package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
- package/_bmad/bme/_vortex/config.yaml +4 -4
- package/_bmad/bme/_vortex/guides/VORTEX-TEAM-GUIDE.md +441 -0
- package/package.json +17 -8
- package/scripts/archive.js +26 -45
- package/scripts/convoke-check.js +88 -0
- package/scripts/convoke-doctor.js +303 -4
- package/scripts/install-gyre-agents.js +0 -0
- package/scripts/lib/artifact-utils.js +2182 -0
- package/scripts/lib/portfolio/formatters/markdown-formatter.js +40 -0
- package/scripts/lib/portfolio/formatters/terminal-formatter.js +56 -0
- package/scripts/lib/portfolio/portfolio-engine.js +572 -0
- package/scripts/lib/portfolio/rules/artifact-chain-rule.js +156 -0
- package/scripts/lib/portfolio/rules/conflict-resolver.js +99 -0
- package/scripts/lib/portfolio/rules/frontmatter-rule.js +42 -0
- package/scripts/lib/portfolio/rules/git-recency-rule.js +69 -0
- package/scripts/lib/types.js +122 -0
- package/scripts/migrate-artifacts.js +439 -0
- package/scripts/portability/catalog-generator.js +353 -0
- package/scripts/portability/classify-skills.js +646 -0
- package/scripts/portability/convoke-export.js +522 -0
- package/scripts/portability/export-engine.js +1133 -0
- package/scripts/portability/generate-adapters.js +79 -0
- package/scripts/portability/manifest-csv.js +147 -0
- package/scripts/portability/seed-catalog-repo.js +427 -0
- package/scripts/portability/templates/canonical-example.md +102 -0
- package/scripts/portability/templates/canonical-format.md +218 -0
- package/scripts/portability/templates/readme-template.md +72 -0
- package/scripts/portability/test-constants.js +42 -0
- package/scripts/portability/validate-classification.js +529 -0
- package/scripts/portability/validate-exports.js +348 -0
- package/scripts/update/lib/agent-registry.js +35 -0
- package/scripts/update/lib/config-merger.js +140 -10
- package/scripts/update/lib/migration-runner.js +1 -1
- package/scripts/update/lib/refresh-installation.js +293 -8
- package/scripts/update/lib/taxonomy-merger.js +138 -0
- package/scripts/update/lib/utils.js +27 -1
- package/scripts/update/lib/validator.js +114 -4
- package/scripts/update/migrations/2.0.x-to-3.1.0.js +50 -0
- package/scripts/update/migrations/3.0.x-to-3.1.0.js +41 -0
- package/scripts/update/migrations/registry.js +14 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule 2: Infer phase from artifact chain analysis.
|
|
3
|
+
*
|
|
4
|
+
* Priority order (highest first):
|
|
5
|
+
* 1. Epic with all stories done/complete/✅/[x]/strikethrough → complete
|
|
6
|
+
* 2. Epic + sprint artifact → build
|
|
7
|
+
* 3. Architecture doc → planning
|
|
8
|
+
* 4. HC artifacts (HC2-HC6) → discovery
|
|
9
|
+
* 5. PRD or brief only → planning
|
|
10
|
+
* 6. No recognized artifacts → unknown
|
|
11
|
+
*
|
|
12
|
+
* Also detects Vortex HC chain completeness (FR34).
|
|
13
|
+
*
|
|
14
|
+
* @param {import('../../types').InitiativeState} state - Current initiative state
|
|
15
|
+
* @param {Array<{filename: string, dir: string, fullPath: string, type?: string, hcPrefix?: string, date?: string, content?: string}>} artifacts
|
|
16
|
+
* @param {Object} _options - Reserved
|
|
17
|
+
* @returns {import('../../types').InitiativeState} Enriched state
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Patterns that indicate epic completion — require status context to avoid false positives.
|
|
21
|
+
* Matches: "status: done", "epic-1: done", "Status:** done", "- [x]", "✅" */
|
|
22
|
+
const DONE_PATTERNS = [
|
|
23
|
+
/(?:status|epic)[^:]*:\s*done\b/i,
|
|
24
|
+
/(?:status|epic)[^:]*:\s*complete\b/i,
|
|
25
|
+
/\*\*\s*done\b/i, // bold marker: **done**
|
|
26
|
+
/✅/,
|
|
27
|
+
/\[x\]/i,
|
|
28
|
+
/~~[^~]{3,}~~/ // strikethrough (min 3 chars to avoid false matches)
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function applyArtifactChainRule(state, artifacts, _options = {}) {
|
|
32
|
+
// Don't override explicit frontmatter phase
|
|
33
|
+
if (state.phase.confidence === 'explicit') return state;
|
|
34
|
+
|
|
35
|
+
const types = new Set(artifacts.map(a => a.type).filter(Boolean));
|
|
36
|
+
const hcPrefixes = new Set(artifacts.map(a => a.hcPrefix).filter(Boolean));
|
|
37
|
+
|
|
38
|
+
// Track last artifact for this initiative
|
|
39
|
+
let latestArtifact = null;
|
|
40
|
+
let latestDate = '';
|
|
41
|
+
for (const a of artifacts) {
|
|
42
|
+
const d = a.date || '';
|
|
43
|
+
if (d >= latestDate) {
|
|
44
|
+
latestDate = d;
|
|
45
|
+
latestArtifact = a;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (latestArtifact) {
|
|
49
|
+
state.lastArtifact = { file: latestArtifact.filename, date: latestDate || 'unknown' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check for epic completion
|
|
53
|
+
const epicArtifacts = artifacts.filter(a => a.type === 'epic');
|
|
54
|
+
if (epicArtifacts.length > 0) {
|
|
55
|
+
// Use most recent epic (by date, fallback to last in array)
|
|
56
|
+
const epic = epicArtifacts.reduce((best, a) => {
|
|
57
|
+
return (a.date || '') >= (best.date || '') ? a : best;
|
|
58
|
+
}, epicArtifacts[0]);
|
|
59
|
+
|
|
60
|
+
if (epic.content && isEpicDone(epic.content)) {
|
|
61
|
+
state.phase = { value: 'complete', source: 'artifact-chain', confidence: 'inferred' };
|
|
62
|
+
return state;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Epic exists + sprint artifact → build
|
|
66
|
+
if (types.has('sprint')) {
|
|
67
|
+
state.phase = { value: 'build', source: 'artifact-chain', confidence: 'inferred' };
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Architecture doc → planning
|
|
73
|
+
if (types.has('arch')) {
|
|
74
|
+
state.phase = { value: 'planning', source: 'artifact-chain', confidence: 'inferred' };
|
|
75
|
+
// Detect HC chain for nextAction even in planning phase
|
|
76
|
+
detectHCChain(state, hcPrefixes);
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// HC artifacts → discovery
|
|
81
|
+
if (hcPrefixes.size > 0) {
|
|
82
|
+
state.phase = { value: 'discovery', source: 'artifact-chain', confidence: 'inferred' };
|
|
83
|
+
detectHCChain(state, hcPrefixes);
|
|
84
|
+
return state;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// PRD or brief → planning
|
|
88
|
+
if (types.has('prd') || types.has('brief')) {
|
|
89
|
+
state.phase = { value: 'planning', source: 'artifact-chain', confidence: 'inferred' };
|
|
90
|
+
return state;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// No recognized pattern — collect evidence so the operator can see WHY it's unknown (Story 6.3)
|
|
94
|
+
const evidence = collectPhaseEvidence(artifacts, types, hcPrefixes);
|
|
95
|
+
state.phase = { value: 'unknown', source: 'artifact-chain', confidence: 'inferred', evidence };
|
|
96
|
+
return state;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Collect a one-line description of what the phase inference looked at when it
|
|
101
|
+
* couldn't determine a phase. Used to populate the Next Action with context
|
|
102
|
+
* instead of the generic "Create PRD or brief" message.
|
|
103
|
+
*
|
|
104
|
+
* @param {Array<Object>} artifacts - All artifacts for this initiative
|
|
105
|
+
* @param {Set<string>} types - Distinct artifact types present
|
|
106
|
+
* @param {Set<string>} hcPrefixes - HC prefixes present (e.g. 'hc1', 'hc2')
|
|
107
|
+
* @returns {string[]} Evidence list, e.g. ["3 artifacts found", "no PRD/brief", "no HC chain", ...]
|
|
108
|
+
*/
|
|
109
|
+
function collectPhaseEvidence(artifacts, types, hcPrefixes) {
|
|
110
|
+
const evidence = [];
|
|
111
|
+
const count = artifacts.length;
|
|
112
|
+
evidence.push(count === 1 ? '1 artifact found' : `${count} artifacts found`);
|
|
113
|
+
|
|
114
|
+
// Special-case: only HC1 present (incomplete discovery, doesn't trigger discovery branch)
|
|
115
|
+
if (hcPrefixes.size === 1 && hcPrefixes.has('hc1')) {
|
|
116
|
+
evidence.push('incomplete HC chain (needs HC2-HC6)');
|
|
117
|
+
return evidence;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!types.has('prd') && !types.has('brief')) evidence.push('no PRD/brief');
|
|
121
|
+
if (!types.has('arch')) evidence.push('no architecture');
|
|
122
|
+
if (hcPrefixes.size === 0) evidence.push('no HC chain');
|
|
123
|
+
if (!types.has('epic')) evidence.push('no epic');
|
|
124
|
+
|
|
125
|
+
return evidence;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if epic content indicates completion via flexible markers.
|
|
130
|
+
* @param {string} content - Epic file content
|
|
131
|
+
* @returns {boolean}
|
|
132
|
+
*/
|
|
133
|
+
function isEpicDone(content) {
|
|
134
|
+
return DONE_PATTERNS.some(pattern => pattern.test(content));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Detect Vortex HC chain completeness and set nextAction if gaps found.
|
|
139
|
+
* HC chain: HC2 (Problem Definition) → HC3 (Hypothesis) → HC4 (Experiment) → HC5 (Signal) → HC6 (Decision)
|
|
140
|
+
* @param {import('../../types').InitiativeState} state
|
|
141
|
+
* @param {Set<string>} hcPrefixes - Set of HC prefixes present (e.g., 'hc2', 'hc3')
|
|
142
|
+
*/
|
|
143
|
+
function detectHCChain(state, hcPrefixes) {
|
|
144
|
+
const expectedHCs = ['hc2', 'hc3', 'hc4', 'hc5', 'hc6'];
|
|
145
|
+
const hcNames = { hc2: 'Problem Definition', hc3: 'Hypothesis', hc4: 'Experiment', hc5: 'Signal', hc6: 'Decision' };
|
|
146
|
+
const missing = expectedHCs.filter(hc => !hcPrefixes.has(hc));
|
|
147
|
+
|
|
148
|
+
if (missing.length === 0) {
|
|
149
|
+
state.nextAction = { value: 'HC chain complete — ready for learning decision', source: 'chain-gap' };
|
|
150
|
+
} else if (missing.length < expectedHCs.length) {
|
|
151
|
+
const nextMissing = missing[0];
|
|
152
|
+
state.nextAction = { value: `Next: ${hcNames[nextMissing]} (${nextMissing.toUpperCase()})`, source: 'chain-gap' };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { applyArtifactChainRule, isEpicDone, detectHCChain, collectPhaseEvidence, DONE_PATTERNS };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule 4: Resolve conflicts between inference signals.
|
|
3
|
+
*
|
|
4
|
+
* - Explicit confidence always wins over inferred
|
|
5
|
+
* - For same confidence: later phase overrides earlier
|
|
6
|
+
* - Ensures lastArtifact and nextAction are populated
|
|
7
|
+
*
|
|
8
|
+
* @param {import('../../types').InitiativeState} state - Current initiative state
|
|
9
|
+
* @param {Array<{filename: string, dir: string, fullPath: string}>} artifacts - Artifacts for this initiative
|
|
10
|
+
* @param {Object} _options - Reserved
|
|
11
|
+
* @returns {import('../../types').InitiativeState} Enriched state
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Phase priority order (higher index = later phase) */
|
|
15
|
+
const PHASE_PRIORITY = ['unknown', 'discovery', 'planning', 'build', 'complete'];
|
|
16
|
+
|
|
17
|
+
function applyConflictResolver(state, artifacts, _options = {}) {
|
|
18
|
+
// Ensure phase has a value
|
|
19
|
+
if (!state.phase.value) {
|
|
20
|
+
state.phase = { value: 'unknown', source: 'conflict-resolver', confidence: 'inferred' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Ensure status has a value
|
|
24
|
+
if (!state.status.value) {
|
|
25
|
+
state.status = { value: 'unknown', source: 'conflict-resolver', confidence: 'inferred' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Ensure lastArtifact is populated
|
|
29
|
+
if (!state.lastArtifact.file && artifacts.length > 0) {
|
|
30
|
+
// Fallback to last artifact in array
|
|
31
|
+
const last = artifacts[artifacts.length - 1];
|
|
32
|
+
state.lastArtifact = { file: last.filename, date: last.date || 'unknown' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Story 6.3: If phase is unknown AND a recognized-type artifact exists AND we have evidence,
|
|
36
|
+
// surface a context-aware next action instead of the generic "Create PRD or brief".
|
|
37
|
+
// - Initiatives with zero artifacts still get the generic message (legitimate use case).
|
|
38
|
+
// - Initiatives whose ONLY artifacts are fallback-attributed (synthetic 'unknown' type)
|
|
39
|
+
// also get the generic message — those don't reflect a real phase signal worth elaborating on.
|
|
40
|
+
// This guards against the design-intent inversion caught in code review:
|
|
41
|
+
// a single fallback-attributed note shouldn't override "Create PRD or brief".
|
|
42
|
+
const hasRecognizedArtifact = artifacts.some(a => a && a.type && a.type !== 'unknown');
|
|
43
|
+
if (
|
|
44
|
+
state.phase.value === 'unknown' &&
|
|
45
|
+
Array.isArray(state.phase.evidence) &&
|
|
46
|
+
state.phase.evidence.length > 0 &&
|
|
47
|
+
hasRecognizedArtifact
|
|
48
|
+
) {
|
|
49
|
+
const summary = state.phase.evidence.slice(0, 2).join(', ');
|
|
50
|
+
state.nextAction = {
|
|
51
|
+
value: `Unknown phase: ${summary}`,
|
|
52
|
+
source: 'conflict-resolver'
|
|
53
|
+
};
|
|
54
|
+
return state;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Derive nextAction from phase if not already set by chain-gap analysis
|
|
58
|
+
if (!state.nextAction.value) {
|
|
59
|
+
state.nextAction = deriveNextAction(state);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return state;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Derive a suggested next action based on current phase.
|
|
67
|
+
* @param {import('../../types').InitiativeState} state
|
|
68
|
+
* @returns {{value: string, source: string}}
|
|
69
|
+
*/
|
|
70
|
+
function deriveNextAction(state) {
|
|
71
|
+
switch (state.phase.value) {
|
|
72
|
+
case 'unknown':
|
|
73
|
+
return { value: 'Create PRD or brief to start planning', source: 'conflict-resolver' };
|
|
74
|
+
case 'discovery':
|
|
75
|
+
return { value: 'Continue discovery — check HC chain progress', source: 'conflict-resolver' };
|
|
76
|
+
case 'planning':
|
|
77
|
+
return { value: 'Create architecture or epics to advance to build', source: 'conflict-resolver' };
|
|
78
|
+
case 'build':
|
|
79
|
+
return { value: 'Continue story execution', source: 'conflict-resolver' };
|
|
80
|
+
case 'complete':
|
|
81
|
+
return { value: 'Initiative complete — consider retrospective', source: 'conflict-resolver' };
|
|
82
|
+
default:
|
|
83
|
+
return { value: 'Review initiative status', source: 'conflict-resolver' };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compare two phases by priority.
|
|
89
|
+
* @param {string} a - Phase name
|
|
90
|
+
* @param {string} b - Phase name
|
|
91
|
+
* @returns {number} Negative if a < b, positive if a > b, 0 if equal
|
|
92
|
+
*/
|
|
93
|
+
function comparePhasePriority(a, b) {
|
|
94
|
+
const idxA = PHASE_PRIORITY.indexOf(a);
|
|
95
|
+
const idxB = PHASE_PRIORITY.indexOf(b);
|
|
96
|
+
return (idxA === -1 ? -1 : idxA) - (idxB === -1 ? -1 : idxB);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { applyConflictResolver, deriveNextAction, comparePhasePriority, PHASE_PRIORITY };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule 1: Read explicit status/phase from frontmatter (highest priority).
|
|
3
|
+
*
|
|
4
|
+
* Reads `status` (standard schema field) and `phase` (operator-override, not in standard schema).
|
|
5
|
+
* Explicit frontmatter signals have highest priority — later rules should not override them.
|
|
6
|
+
*
|
|
7
|
+
* @param {import('../../types').InitiativeState} state - Current initiative state
|
|
8
|
+
* @param {Array<{filename: string, dir: string, fullPath: string, frontmatter?: Object}>} artifacts - Artifacts for this initiative
|
|
9
|
+
* @param {Object} _options - Reserved
|
|
10
|
+
* @returns {import('../../types').InitiativeState} Enriched state
|
|
11
|
+
*/
|
|
12
|
+
function applyFrontmatterRule(state, artifacts, _options = {}) {
|
|
13
|
+
// Process artifacts most-recent-first (by date suffix or array order)
|
|
14
|
+
// First explicit value found wins
|
|
15
|
+
for (const artifact of artifacts) {
|
|
16
|
+
if (!artifact.frontmatter) continue;
|
|
17
|
+
|
|
18
|
+
if (!state.status.value || state.status.confidence !== 'explicit') {
|
|
19
|
+
if (artifact.frontmatter.status != null && artifact.frontmatter.status !== '') {
|
|
20
|
+
state.status = {
|
|
21
|
+
value: artifact.frontmatter.status,
|
|
22
|
+
source: 'frontmatter',
|
|
23
|
+
confidence: 'explicit'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!state.phase.value || state.phase.confidence !== 'explicit') {
|
|
29
|
+
if (artifact.frontmatter.phase != null && artifact.frontmatter.phase !== '') {
|
|
30
|
+
state.phase = {
|
|
31
|
+
value: artifact.frontmatter.phase,
|
|
32
|
+
source: 'frontmatter',
|
|
33
|
+
confidence: 'explicit'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { applyFrontmatterRule };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule 3: Infer status from git recency (stale detection).
|
|
3
|
+
*
|
|
4
|
+
* Checks the most recent git commit date for the initiative's artifacts.
|
|
5
|
+
* If within stale_days → ongoing. If beyond → stale.
|
|
6
|
+
*
|
|
7
|
+
* Known limitation: checks current branch only.
|
|
8
|
+
*
|
|
9
|
+
* @param {import('../../types').InitiativeState} state - Current initiative state
|
|
10
|
+
* @param {Array<{filename: string, dir: string, fullPath: string}>} artifacts - Artifacts for this initiative
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {number} [options.staleDays=30] - Days threshold for stale detection
|
|
13
|
+
* @param {string} options.projectRoot - Absolute path to project root
|
|
14
|
+
* @returns {import('../../types').InitiativeState} Enriched state
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { execFileSync } = require('child_process');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
function applyGitRecencyRule(state, artifacts, options = {}) {
|
|
21
|
+
// Don't override explicit frontmatter status
|
|
22
|
+
if (state.status.confidence === 'explicit') return state;
|
|
23
|
+
|
|
24
|
+
const { staleDays = 30, projectRoot } = options;
|
|
25
|
+
if (!projectRoot || artifacts.length === 0) return state;
|
|
26
|
+
|
|
27
|
+
// Find most recent git activity across all artifacts for this initiative
|
|
28
|
+
let latestDate = null;
|
|
29
|
+
let latestFile = null;
|
|
30
|
+
|
|
31
|
+
for (const artifact of artifacts) {
|
|
32
|
+
try {
|
|
33
|
+
const relativePath = path.relative(projectRoot, artifact.fullPath);
|
|
34
|
+
const dateStr = execFileSync(
|
|
35
|
+
'git', ['log', '-1', '--format=%as', '--', relativePath],
|
|
36
|
+
{ cwd: projectRoot, encoding: 'utf8', stdio: 'pipe' }
|
|
37
|
+
).trim();
|
|
38
|
+
|
|
39
|
+
if (dateStr && (!latestDate || dateStr > latestDate)) {
|
|
40
|
+
latestDate = dateStr;
|
|
41
|
+
latestFile = artifact.filename;
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// File not tracked or git unavailable — skip
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!latestDate) return state;
|
|
49
|
+
|
|
50
|
+
// Update lastArtifact if git date is more recent than artifact-chain's date
|
|
51
|
+
if (!state.lastArtifact.file || latestDate > (state.lastArtifact.date || '')) {
|
|
52
|
+
state.lastArtifact = { file: latestFile, date: latestDate };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Calculate days since last activity
|
|
56
|
+
const lastActivity = new Date(latestDate);
|
|
57
|
+
const now = new Date();
|
|
58
|
+
const daysSince = Math.floor((now - lastActivity) / (1000 * 60 * 60 * 24));
|
|
59
|
+
|
|
60
|
+
if (daysSince <= staleDays) {
|
|
61
|
+
state.status = { value: 'ongoing', source: 'git-recency', confidence: 'inferred' };
|
|
62
|
+
} else {
|
|
63
|
+
state.status = { value: 'stale', source: 'git-recency', confidence: 'inferred' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return state;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { applyGitRecencyRule };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared JSDoc type definitions for the Artifact Governance & Portfolio system.
|
|
3
|
+
* Used by: artifact-utils.js, migrate-artifacts.js, portfolio-engine.js, archive.js
|
|
4
|
+
*
|
|
5
|
+
* @module types
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Inference signal with value, source, and confidence level.
|
|
10
|
+
* @typedef {Object} InferenceSignal
|
|
11
|
+
* @property {string} value - The inferred value
|
|
12
|
+
* @property {string} source - Where the inference came from (e.g., 'frontmatter', 'artifact-chain', 'git-recency')
|
|
13
|
+
* @property {'explicit'|'inferred'} confidence - Whether this is from explicit data or heuristic inference
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* State of an initiative as derived by the portfolio inference rule chain.
|
|
18
|
+
* This is the core data structure that flows through the rule chain and into formatters.
|
|
19
|
+
* @typedef {Object} InitiativeState
|
|
20
|
+
* @property {string} initiative - Initiative ID from taxonomy (e.g., 'helm', 'gyre')
|
|
21
|
+
* @property {InferenceSignal} phase - Current phase: discovery, planning, build, blocked, complete, unknown
|
|
22
|
+
* @property {InferenceSignal} status - Current status: ongoing, blocked, paused, complete, stale, unknown
|
|
23
|
+
* @property {{file: string, date: string}} lastArtifact - Most recently modified artifact for this initiative
|
|
24
|
+
* @property {{value: string, source: string}} nextAction - Suggested next action based on chain gap analysis
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Use ManifestEntry instead. Planning placeholder with incomplete fields.
|
|
29
|
+
* @typedef {Object} RenameManifestEntry
|
|
30
|
+
* @property {string} oldPath - Current file path (relative to project root)
|
|
31
|
+
* @property {string} newPath - Proposed new file path
|
|
32
|
+
* @property {string} initiative - Inferred initiative ID
|
|
33
|
+
* @property {string} artifactType - Inferred artifact type
|
|
34
|
+
* @property {'high'|'low'} confidence - Inference confidence level
|
|
35
|
+
* @property {'fully-governed'|'half-governed'|'ungoverned'|'invalid-governed'} governanceState - Current governance state
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Manifest entry for dry-run display. Replaces RenameManifestEntry.
|
|
40
|
+
* @typedef {Object} ManifestEntry
|
|
41
|
+
* @property {string} oldPath - Current relative path (e.g., 'planning-artifacts/prd-gyre.md')
|
|
42
|
+
* @property {string|null} newPath - Proposed new path (null for SKIP/CONFLICT/AMBIGUOUS)
|
|
43
|
+
* @property {string|null} initiative - Resolved initiative ID (null if ambiguous)
|
|
44
|
+
* @property {string|null} artifactType - Resolved artifact type (null if ungoverned)
|
|
45
|
+
* @property {'high'|'low'} confidence - Initiative inference confidence
|
|
46
|
+
* @property {string} source - Inference source (exact/alias/empty/unresolved)
|
|
47
|
+
* @property {'RENAME'|'SKIP'|'INJECT_ONLY'|'CONFLICT'|'AMBIGUOUS'} action
|
|
48
|
+
* @property {string} dir - Directory name (e.g., 'planning-artifacts')
|
|
49
|
+
* @property {{firstLines: string[], gitAuthor: string|null, gitDate: string|null}|null} contextClues
|
|
50
|
+
* @property {string[]|null} crossReferences - Files referencing this one (verbose only)
|
|
51
|
+
* @property {string[]} candidates - Possible initiative matches (ambiguous only)
|
|
52
|
+
* @property {string[]|null} collisionWith - Other files colliding on same newPath
|
|
53
|
+
* @property {string|null} frontmatterInitiative - Initiative from frontmatter (for CONFLICT display)
|
|
54
|
+
* @property {string|null} fileInitiative - Initiative from filename (for CONFLICT display)
|
|
55
|
+
* @property {'high'|'low'} typeConfidence - Artifact type inference confidence
|
|
56
|
+
* @property {string} typeSource - Artifact type inference source (prefix/alias/none)
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Result of generateManifest() containing all entries, collisions, and summary.
|
|
61
|
+
* @typedef {Object} ManifestResult
|
|
62
|
+
* @property {ManifestEntry[]} entries - All manifest entries
|
|
63
|
+
* @property {Map<string, string[]>} collisions - Colliding newPath -> list of oldPaths
|
|
64
|
+
* @property {{total: number, skip: number, rename: number, inject: number, conflict: number, ambiguous: number}} summary
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A markdown link that needs updating after file renames.
|
|
69
|
+
* @typedef {Object} LinkUpdate
|
|
70
|
+
* @property {string} filePath - Path of the file containing the link
|
|
71
|
+
* @property {string} oldLink - Original link target
|
|
72
|
+
* @property {string} newLink - Updated link target
|
|
73
|
+
* @property {'bracket-link'|'relative-link'|'parent-link'|'frontmatter-array'} pattern - Which link pattern matched
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parsed taxonomy configuration from _bmad/_config/taxonomy.yaml.
|
|
78
|
+
* @typedef {Object} TaxonomyConfig
|
|
79
|
+
* @property {{platform: string[], user: string[]}} initiatives - Initiative IDs split by ownership
|
|
80
|
+
* @property {string[]} artifact_types - Valid artifact type identifiers
|
|
81
|
+
* @property {Object<string, string>} aliases - Historical name → canonical initiative ID mapping (migration-only)
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Frontmatter metadata fields for governed artifacts.
|
|
86
|
+
* @typedef {Object} FrontmatterSchema
|
|
87
|
+
* @property {string} initiative - Initiative ID from taxonomy
|
|
88
|
+
* @property {string} artifact_type - Artifact type from taxonomy
|
|
89
|
+
* @property {'draft'|'validated'|'superseded'|'active'} [status] - Optional artifact-level status
|
|
90
|
+
* @property {string} created - ISO 8601 date string (YYYY-MM-DD)
|
|
91
|
+
* @property {number} schema_version - Schema version integer (currently 1)
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Result of parsing a filename against naming conventions.
|
|
96
|
+
* @typedef {Object} ParsedFilename
|
|
97
|
+
* @property {string} filename - Original filename
|
|
98
|
+
* @property {boolean} isDated - Whether the file has a date suffix
|
|
99
|
+
* @property {string|null} date - Extracted date (YYYY-MM-DD) or null
|
|
100
|
+
* @property {string} baseName - Filename without date and extension
|
|
101
|
+
* @property {string|null} category - Extracted category prefix or null
|
|
102
|
+
* @property {boolean} hasValidCategory - Whether category is in the valid list
|
|
103
|
+
* @property {boolean} isUppercase - Whether filename contains uppercase characters
|
|
104
|
+
* @property {boolean} matchesConvention - Whether filename fully matches naming convention
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Result of a frontmatter conflict check.
|
|
109
|
+
* @typedef {Object} FrontmatterConflict
|
|
110
|
+
* @property {string} field - The conflicting field name
|
|
111
|
+
* @property {*} existingValue - Current value in frontmatter
|
|
112
|
+
* @property {*} newValue - Proposed new value
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Result of injectFrontmatter() including conflict detection.
|
|
117
|
+
* @typedef {Object} InjectResult
|
|
118
|
+
* @property {string} content - The modified file content with injected frontmatter
|
|
119
|
+
* @property {FrontmatterConflict[]} conflicts - Any field conflicts detected (empty if none)
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
module.exports = {};
|