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.
Files changed (92) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +14 -13
  3. package/_bmad/bme/_artifacts/config.yaml +15 -0
  4. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
  5. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
  6. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
  7. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
  8. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
  9. package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
  10. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
  11. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
  12. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
  13. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
  14. package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
  15. package/_bmad/bme/_gyre/guides/GYRE-TEAM-GUIDE.md +506 -0
  16. package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
  17. package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
  18. package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
  19. package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
  20. package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
  21. package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
  22. package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
  23. package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
  24. package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
  25. package/_bmad/bme/_team-factory/config.yaml +13 -0
  26. package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
  27. package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
  28. package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
  29. package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
  30. package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
  31. package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
  32. package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
  33. package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
  34. package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
  35. package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
  36. package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
  37. package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
  38. package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
  39. package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
  40. package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
  41. package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
  42. package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
  43. package/_bmad/bme/_team-factory/module-help.csv +3 -0
  44. package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
  45. package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
  46. package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
  47. package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
  48. package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
  49. package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
  50. package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
  51. package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
  52. package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
  53. package/_bmad/bme/_vortex/config.yaml +4 -4
  54. package/_bmad/bme/_vortex/guides/VORTEX-TEAM-GUIDE.md +441 -0
  55. package/package.json +17 -8
  56. package/scripts/archive.js +26 -45
  57. package/scripts/convoke-check.js +88 -0
  58. package/scripts/convoke-doctor.js +303 -4
  59. package/scripts/install-gyre-agents.js +0 -0
  60. package/scripts/lib/artifact-utils.js +2182 -0
  61. package/scripts/lib/portfolio/formatters/markdown-formatter.js +40 -0
  62. package/scripts/lib/portfolio/formatters/terminal-formatter.js +56 -0
  63. package/scripts/lib/portfolio/portfolio-engine.js +572 -0
  64. package/scripts/lib/portfolio/rules/artifact-chain-rule.js +156 -0
  65. package/scripts/lib/portfolio/rules/conflict-resolver.js +99 -0
  66. package/scripts/lib/portfolio/rules/frontmatter-rule.js +42 -0
  67. package/scripts/lib/portfolio/rules/git-recency-rule.js +69 -0
  68. package/scripts/lib/types.js +122 -0
  69. package/scripts/migrate-artifacts.js +439 -0
  70. package/scripts/portability/catalog-generator.js +353 -0
  71. package/scripts/portability/classify-skills.js +646 -0
  72. package/scripts/portability/convoke-export.js +522 -0
  73. package/scripts/portability/export-engine.js +1133 -0
  74. package/scripts/portability/generate-adapters.js +79 -0
  75. package/scripts/portability/manifest-csv.js +147 -0
  76. package/scripts/portability/seed-catalog-repo.js +427 -0
  77. package/scripts/portability/templates/canonical-example.md +102 -0
  78. package/scripts/portability/templates/canonical-format.md +218 -0
  79. package/scripts/portability/templates/readme-template.md +72 -0
  80. package/scripts/portability/test-constants.js +42 -0
  81. package/scripts/portability/validate-classification.js +529 -0
  82. package/scripts/portability/validate-exports.js +348 -0
  83. package/scripts/update/lib/agent-registry.js +35 -0
  84. package/scripts/update/lib/config-merger.js +140 -10
  85. package/scripts/update/lib/migration-runner.js +1 -1
  86. package/scripts/update/lib/refresh-installation.js +293 -8
  87. package/scripts/update/lib/taxonomy-merger.js +138 -0
  88. package/scripts/update/lib/utils.js +27 -1
  89. package/scripts/update/lib/validator.js +114 -4
  90. package/scripts/update/migrations/2.0.x-to-3.1.0.js +50 -0
  91. package/scripts/update/migrations/3.0.x-to-3.1.0.js +41 -0
  92. 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 = {};