@planu/cli 1.12.0 → 1.13.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/dist/config/ai-tool-registry.json +71 -0
- package/dist/config/autopilot-config.json +21 -0
- package/dist/config/competitive-catalog.json +83 -0
- package/dist/config/license-plans.json +17 -1
- package/dist/engine/agent-registry/lifecycle-manager.d.ts +8 -0
- package/dist/engine/agent-registry/lifecycle-manager.d.ts.map +1 -0
- package/dist/engine/agent-registry/lifecycle-manager.js +81 -0
- package/dist/engine/agent-registry/lifecycle-manager.js.map +1 -0
- package/dist/engine/agent-registry/role-catalog.d.ts +17 -0
- package/dist/engine/agent-registry/role-catalog.d.ts.map +1 -0
- package/dist/engine/agent-registry/role-catalog.js +55 -0
- package/dist/engine/agent-registry/role-catalog.js.map +1 -0
- package/dist/engine/autopilot/action-executor.d.ts +18 -0
- package/dist/engine/autopilot/action-executor.d.ts.map +1 -0
- package/dist/engine/autopilot/action-executor.js +91 -0
- package/dist/engine/autopilot/action-executor.js.map +1 -0
- package/dist/engine/autopilot/event-bus.d.ts +8 -0
- package/dist/engine/autopilot/event-bus.d.ts.map +1 -0
- package/dist/engine/autopilot/event-bus.js +28 -0
- package/dist/engine/autopilot/event-bus.js.map +1 -0
- package/dist/engine/autopilot/trigger-rules.d.ts +3 -0
- package/dist/engine/autopilot/trigger-rules.d.ts.map +1 -0
- package/dist/engine/autopilot/trigger-rules.js +125 -0
- package/dist/engine/autopilot/trigger-rules.js.map +1 -0
- package/dist/engine/competitive/gap-analyzer.d.ts +12 -0
- package/dist/engine/competitive/gap-analyzer.d.ts.map +1 -0
- package/dist/engine/competitive/gap-analyzer.js +214 -0
- package/dist/engine/competitive/gap-analyzer.js.map +1 -0
- package/dist/engine/hook-generator/ai-hook-templates.d.ts +8 -0
- package/dist/engine/hook-generator/ai-hook-templates.d.ts.map +1 -0
- package/dist/engine/hook-generator/ai-hook-templates.js +43 -0
- package/dist/engine/hook-generator/ai-hook-templates.js.map +1 -0
- package/dist/engine/hook-generator/hook-merger.d.ts +13 -0
- package/dist/engine/hook-generator/hook-merger.d.ts.map +1 -0
- package/dist/engine/hook-generator/hook-merger.js +148 -0
- package/dist/engine/hook-generator/hook-merger.js.map +1 -0
- package/dist/engine/hook-generator/stack-hook-templates.d.ts +10 -0
- package/dist/engine/hook-generator/stack-hook-templates.d.ts.map +1 -0
- package/dist/engine/hook-generator/stack-hook-templates.js +105 -0
- package/dist/engine/hook-generator/stack-hook-templates.js.map +1 -0
- package/dist/engine/project-dna/ai-tool-detector.d.ts +12 -0
- package/dist/engine/project-dna/ai-tool-detector.d.ts.map +1 -0
- package/dist/engine/project-dna/ai-tool-detector.js +103 -0
- package/dist/engine/project-dna/ai-tool-detector.js.map +1 -0
- package/dist/engine/project-dna/rules-generator.d.ts +18 -0
- package/dist/engine/project-dna/rules-generator.d.ts.map +1 -0
- package/dist/engine/project-dna/rules-generator.js +193 -0
- package/dist/engine/project-dna/rules-generator.js.map +1 -0
- package/dist/engine/project-dna/stack-detector.d.ts +24 -0
- package/dist/engine/project-dna/stack-detector.d.ts.map +1 -0
- package/dist/engine/project-dna/stack-detector.js +309 -0
- package/dist/engine/project-dna/stack-detector.js.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/agent-registry-store.d.ts +11 -0
- package/dist/storage/agent-registry-store.d.ts.map +1 -0
- package/dist/storage/agent-registry-store.js +45 -0
- package/dist/storage/agent-registry-store.js.map +1 -0
- package/dist/tools/competitive-handlers.d.ts +30 -0
- package/dist/tools/competitive-handlers.d.ts.map +1 -0
- package/dist/tools/competitive-handlers.js +155 -0
- package/dist/tools/competitive-handlers.js.map +1 -0
- package/dist/tools/create-spec/post-creation.d.ts +1 -1
- package/dist/tools/create-spec/post-creation.d.ts.map +1 -1
- package/dist/tools/create-spec/post-creation.js +13 -1
- package/dist/tools/create-spec/post-creation.js.map +1 -1
- package/dist/tools/create-spec.js +1 -1
- package/dist/tools/create-spec.js.map +1 -1
- package/dist/tools/hook-generator-handler.d.ts +8 -0
- package/dist/tools/hook-generator-handler.d.ts.map +1 -0
- package/dist/tools/hook-generator-handler.js +154 -0
- package/dist/tools/hook-generator-handler.js.map +1 -0
- package/dist/tools/project-dna-handler.d.ts +34 -0
- package/dist/tools/project-dna-handler.d.ts.map +1 -0
- package/dist/tools/project-dna-handler.js +261 -0
- package/dist/tools/project-dna-handler.js.map +1 -0
- package/dist/tools/register-agent-registry.d.ts +5 -0
- package/dist/tools/register-agent-registry.d.ts.map +1 -0
- package/dist/tools/register-agent-registry.js +254 -0
- package/dist/tools/register-agent-registry.js.map +1 -0
- package/dist/tools/register-autopilot.d.ts +3 -0
- package/dist/tools/register-autopilot.d.ts.map +1 -0
- package/dist/tools/register-autopilot.js +78 -0
- package/dist/tools/register-autopilot.js.map +1 -0
- package/dist/tools/register-competitive.d.ts +3 -0
- package/dist/tools/register-competitive.d.ts.map +1 -0
- package/dist/tools/register-competitive.js +88 -0
- package/dist/tools/register-competitive.js.map +1 -0
- package/dist/tools/register-hook-generator.d.ts +3 -0
- package/dist/tools/register-hook-generator.d.ts.map +1 -0
- package/dist/tools/register-hook-generator.js +96 -0
- package/dist/tools/register-hook-generator.js.map +1 -0
- package/dist/tools/register-project-dna.d.ts +3 -0
- package/dist/tools/register-project-dna.d.ts.map +1 -0
- package/dist/tools/register-project-dna.js +43 -0
- package/dist/tools/register-project-dna.js.map +1 -0
- package/dist/tools/update-status/side-effects.d.ts.map +1 -1
- package/dist/tools/update-status/side-effects.js +32 -0
- package/dist/tools/update-status/side-effects.js.map +1 -1
- package/dist/types/agent-registry.d.ts +53 -0
- package/dist/types/agent-registry.d.ts.map +1 -0
- package/dist/types/agent-registry.js +2 -0
- package/dist/types/agent-registry.js.map +1 -0
- package/dist/types/autopilot.d.ts +36 -0
- package/dist/types/autopilot.d.ts.map +1 -0
- package/dist/types/autopilot.js +3 -0
- package/dist/types/autopilot.js.map +1 -0
- package/dist/types/competitive.d.ts +41 -0
- package/dist/types/competitive.d.ts.map +1 -0
- package/dist/types/competitive.js +3 -0
- package/dist/types/competitive.js.map +1 -0
- package/dist/types/hook-generator.d.ts +49 -0
- package/dist/types/hook-generator.d.ts.map +1 -0
- package/dist/types/hook-generator.js +3 -0
- package/dist/types/hook-generator.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/project-dna.d.ts +46 -0
- package/dist/types/project-dna.d.ts.map +1 -0
- package/dist/types/project-dna.js +4 -0
- package/dist/types/project-dna.js.map +1 -0
- package/package.json +1 -1
- package/src/config/ai-tool-registry.json +71 -0
- package/src/config/autopilot-config.json +21 -0
- package/src/config/competitive-catalog.json +83 -0
- package/src/config/license-plans.json +17 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// engine/autopilot/trigger-rules.ts — SPEC-413: Catalog of default trigger rules
|
|
2
|
+
export const DEFAULT_TRIGGER_RULES = [
|
|
3
|
+
// ── post spec:created ──────────────────────────────────────────────────────
|
|
4
|
+
{
|
|
5
|
+
id: 'post-create:validate_criteria_quality',
|
|
6
|
+
event: 'spec:created',
|
|
7
|
+
action: 'validate_criteria_quality',
|
|
8
|
+
enabled: true,
|
|
9
|
+
description: 'Scores EARS quality of acceptance criteria after spec creation',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'post-create:suggest_criteria',
|
|
13
|
+
event: 'spec:created',
|
|
14
|
+
action: 'suggest_criteria',
|
|
15
|
+
enabled: true,
|
|
16
|
+
description: 'Suggests missing domain criteria (security, compliance, edge cases)',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'post-create:detect_contradictions',
|
|
20
|
+
event: 'spec:created',
|
|
21
|
+
action: 'detect_contradictions',
|
|
22
|
+
enabled: true,
|
|
23
|
+
description: 'Runs full contradiction detection on new spec criteria',
|
|
24
|
+
},
|
|
25
|
+
// ── post spec:status:approved ──────────────────────────────────────────────
|
|
26
|
+
{
|
|
27
|
+
id: 'post-approved:challenge_spec',
|
|
28
|
+
event: 'spec:status:approved',
|
|
29
|
+
action: 'challenge_spec',
|
|
30
|
+
enabled: true,
|
|
31
|
+
condition: { minEstimatedHours: 20, riskLevel: ['high', 'critical'] },
|
|
32
|
+
description: 'Stress-tests approved specs with >20h estimate or high risk',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'post-approved:spec_quality_score',
|
|
36
|
+
event: 'spec:status:approved',
|
|
37
|
+
action: 'spec_quality_score',
|
|
38
|
+
enabled: true,
|
|
39
|
+
description: 'Generates A-F quality score at approval time',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'post-approved:generate_orchestration_plan',
|
|
43
|
+
event: 'spec:status:approved',
|
|
44
|
+
action: 'generate_orchestration_plan',
|
|
45
|
+
enabled: true,
|
|
46
|
+
condition: { minEstimatedHours: 20 },
|
|
47
|
+
description: 'Suggests multi-agent plan for large specs (>20h)',
|
|
48
|
+
},
|
|
49
|
+
// ── post spec:status:implementing ─────────────────────────────────────────
|
|
50
|
+
{
|
|
51
|
+
id: 'post-implementing:tdd_scaffold',
|
|
52
|
+
event: 'spec:status:implementing',
|
|
53
|
+
action: 'tdd_scaffold',
|
|
54
|
+
enabled: true,
|
|
55
|
+
description: 'Generates test scaffold from acceptance criteria when implementation starts',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'post-implementing:watch_spec_drift_start',
|
|
59
|
+
event: 'spec:status:implementing',
|
|
60
|
+
action: 'watch_spec_drift',
|
|
61
|
+
enabled: true,
|
|
62
|
+
description: 'Starts file watcher for real-time drift detection',
|
|
63
|
+
},
|
|
64
|
+
// ── post spec:status:done ──────────────────────────────────────────────────
|
|
65
|
+
{
|
|
66
|
+
id: 'post-done:watch_spec_drift_stop',
|
|
67
|
+
event: 'spec:status:done',
|
|
68
|
+
action: 'watch_spec_drift',
|
|
69
|
+
enabled: true,
|
|
70
|
+
description: 'Stops drift watcher when spec is marked done',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'post-done:auto_fix_health',
|
|
74
|
+
event: 'spec:status:done',
|
|
75
|
+
action: 'auto_fix_health',
|
|
76
|
+
enabled: true,
|
|
77
|
+
description: 'Auto-repairs deterministic health issues post-done',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'post-done:generate_changelog',
|
|
81
|
+
event: 'spec:status:done',
|
|
82
|
+
action: 'generate_changelog',
|
|
83
|
+
enabled: true,
|
|
84
|
+
description: 'Generates changelog entry when spec completes',
|
|
85
|
+
},
|
|
86
|
+
// ── drift:detected ────────────────────────────────────────────────────────
|
|
87
|
+
{
|
|
88
|
+
id: 'drift-detected:resolve_drift_violations',
|
|
89
|
+
event: 'drift:detected',
|
|
90
|
+
action: 'resolve_drift_violations',
|
|
91
|
+
enabled: true,
|
|
92
|
+
condition: { minDriftScore: 0.3 },
|
|
93
|
+
description: 'Auto-resolves drift violations when score exceeds threshold',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'drift-detected:log_lesson',
|
|
97
|
+
event: 'drift:detected',
|
|
98
|
+
action: 'log_lesson',
|
|
99
|
+
enabled: true,
|
|
100
|
+
description: 'Logs lesson learned when drift is detected',
|
|
101
|
+
},
|
|
102
|
+
// ── cron:daily ────────────────────────────────────────────────────────────
|
|
103
|
+
{
|
|
104
|
+
id: 'cron:health_check_all',
|
|
105
|
+
event: 'cron:daily',
|
|
106
|
+
action: 'health_check_all',
|
|
107
|
+
enabled: false,
|
|
108
|
+
description: 'Daily health check across all specs (opt-in)',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'cron:velocity_report',
|
|
112
|
+
event: 'cron:daily',
|
|
113
|
+
action: 'velocity_report',
|
|
114
|
+
enabled: false,
|
|
115
|
+
description: 'Daily velocity metrics (opt-in)',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'cron:estimation_accuracy_report',
|
|
119
|
+
event: 'cron:daily',
|
|
120
|
+
action: 'estimation_accuracy_report',
|
|
121
|
+
enabled: false,
|
|
122
|
+
description: 'Daily estimation calibration (opt-in)',
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
//# sourceMappingURL=trigger-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger-rules.js","sourceRoot":"","sources":["../../../src/engine/autopilot/trigger-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CompetitiveCatalog, GapAnalysisResult } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts lowercase keywords from free text, removing stop words and
|
|
4
|
+
* short tokens. Returns a deduplicated array.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractKeywords(text: string): string[];
|
|
7
|
+
/**
|
|
8
|
+
* Analyzes a feature (expressed as keywords) against all competitors in the
|
|
9
|
+
* catalog and returns a structured GapAnalysisResult.
|
|
10
|
+
*/
|
|
11
|
+
export declare function analyzeGap(featureKeywords: string[], catalog: CompetitiveCatalog): GapAnalysisResult;
|
|
12
|
+
//# sourceMappingURL=gap-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gap-analyzer.d.ts","sourceRoot":"","sources":["../../../src/engine/competitive/gap-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAmHlF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQtD;AA4CD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,eAAe,EAAE,MAAM,EAAE,EACzB,OAAO,EAAE,kBAAkB,GAC1B,iBAAiB,CAyEnB"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Planu unique capabilities — source of truth
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
const PLANU_CAPABILITIES = [
|
|
5
|
+
'mcp-protocol',
|
|
6
|
+
'spec-driven-development',
|
|
7
|
+
'drift-detection',
|
|
8
|
+
'drift-watcher',
|
|
9
|
+
'compliance-reporting',
|
|
10
|
+
'ears-notation',
|
|
11
|
+
'workflow-checkpoints',
|
|
12
|
+
'context-profiles',
|
|
13
|
+
'competitive-gap-analysis',
|
|
14
|
+
'velocity-metrics',
|
|
15
|
+
'auto-fix-health',
|
|
16
|
+
'license-tiers',
|
|
17
|
+
'multi-language-docs',
|
|
18
|
+
'ai-code-alignment',
|
|
19
|
+
'spec-health-scoring',
|
|
20
|
+
'spec-history',
|
|
21
|
+
'bdd-gherkin',
|
|
22
|
+
'tdd-scaffold',
|
|
23
|
+
'coverage-gap-analysis',
|
|
24
|
+
'red-team',
|
|
25
|
+
'challenge-spec',
|
|
26
|
+
'require-checkpoint',
|
|
27
|
+
'approve-checkpoint',
|
|
28
|
+
'generate-compliance-evidence',
|
|
29
|
+
'pull-from-notion',
|
|
30
|
+
'pull-from-asana',
|
|
31
|
+
'pull-from-monday',
|
|
32
|
+
'validate-criteria-quality',
|
|
33
|
+
'rewrite-criteria-ears',
|
|
34
|
+
'session-checkpoint',
|
|
35
|
+
'audit-claude-config',
|
|
36
|
+
];
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Stop words for keyword extraction
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
const STOP_WORDS = new Set([
|
|
41
|
+
'a',
|
|
42
|
+
'an',
|
|
43
|
+
'the',
|
|
44
|
+
'and',
|
|
45
|
+
'or',
|
|
46
|
+
'is',
|
|
47
|
+
'in',
|
|
48
|
+
'on',
|
|
49
|
+
'at',
|
|
50
|
+
'to',
|
|
51
|
+
'for',
|
|
52
|
+
'of',
|
|
53
|
+
'with',
|
|
54
|
+
'by',
|
|
55
|
+
'from',
|
|
56
|
+
'as',
|
|
57
|
+
'be',
|
|
58
|
+
'are',
|
|
59
|
+
'was',
|
|
60
|
+
'were',
|
|
61
|
+
'has',
|
|
62
|
+
'have',
|
|
63
|
+
'had',
|
|
64
|
+
'do',
|
|
65
|
+
'does',
|
|
66
|
+
'did',
|
|
67
|
+
'will',
|
|
68
|
+
'would',
|
|
69
|
+
'can',
|
|
70
|
+
'could',
|
|
71
|
+
'should',
|
|
72
|
+
'may',
|
|
73
|
+
'might',
|
|
74
|
+
'must',
|
|
75
|
+
'shall',
|
|
76
|
+
'it',
|
|
77
|
+
'its',
|
|
78
|
+
'this',
|
|
79
|
+
'that',
|
|
80
|
+
'which',
|
|
81
|
+
'who',
|
|
82
|
+
'what',
|
|
83
|
+
'when',
|
|
84
|
+
'where',
|
|
85
|
+
'how',
|
|
86
|
+
'not',
|
|
87
|
+
'no',
|
|
88
|
+
'so',
|
|
89
|
+
'if',
|
|
90
|
+
'but',
|
|
91
|
+
'yet',
|
|
92
|
+
'than',
|
|
93
|
+
'then',
|
|
94
|
+
'also',
|
|
95
|
+
'any',
|
|
96
|
+
'all',
|
|
97
|
+
'each',
|
|
98
|
+
'such',
|
|
99
|
+
'we',
|
|
100
|
+
'our',
|
|
101
|
+
'you',
|
|
102
|
+
'your',
|
|
103
|
+
'they',
|
|
104
|
+
'their',
|
|
105
|
+
'he',
|
|
106
|
+
'she',
|
|
107
|
+
'his',
|
|
108
|
+
'her',
|
|
109
|
+
]);
|
|
110
|
+
/**
|
|
111
|
+
* Extracts lowercase keywords from free text, removing stop words and
|
|
112
|
+
* short tokens. Returns a deduplicated array.
|
|
113
|
+
*/
|
|
114
|
+
export function extractKeywords(text) {
|
|
115
|
+
const tokens = text
|
|
116
|
+
.toLowerCase()
|
|
117
|
+
.split(/[\s\-,.:;!?()[\]{}"'/\\|<>@#$%^&*+=~`]+/)
|
|
118
|
+
.map((t) => t.trim())
|
|
119
|
+
.filter((t) => t.length >= 3 && !STOP_WORDS.has(t));
|
|
120
|
+
return [...new Set(tokens)];
|
|
121
|
+
}
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Capability matching helpers
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
/**
|
|
126
|
+
* Returns true if any capability in `capabilities` contains `keyword` as a
|
|
127
|
+
* substring (case-insensitive after normalization).
|
|
128
|
+
*/
|
|
129
|
+
function capabilityMatchesKeyword(capabilities, keyword) {
|
|
130
|
+
const lower = keyword.toLowerCase();
|
|
131
|
+
return capabilities.some((cap) => cap.toLowerCase().includes(lower));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Checks whether a list of capabilities matches at least one of the feature
|
|
135
|
+
* keywords.
|
|
136
|
+
*/
|
|
137
|
+
function hasFeatureMatch(capabilities, keywords) {
|
|
138
|
+
return keywords.some((kw) => capabilityMatchesKeyword(capabilities, kw));
|
|
139
|
+
}
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Positioning thresholds
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
const PARITY_MIN = 2;
|
|
144
|
+
const PARITY_MAX = 4;
|
|
145
|
+
function computePositioning(matchCount) {
|
|
146
|
+
if (matchCount < PARITY_MIN) {
|
|
147
|
+
return 'differentiator';
|
|
148
|
+
}
|
|
149
|
+
if (matchCount <= PARITY_MAX) {
|
|
150
|
+
return 'parity';
|
|
151
|
+
}
|
|
152
|
+
return 'lagging';
|
|
153
|
+
}
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Main analysis function
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
/**
|
|
158
|
+
* Analyzes a feature (expressed as keywords) against all competitors in the
|
|
159
|
+
* catalog and returns a structured GapAnalysisResult.
|
|
160
|
+
*/
|
|
161
|
+
export function analyzeGap(featureKeywords, catalog) {
|
|
162
|
+
const allCompetitors = Object.values(catalog.competitors);
|
|
163
|
+
// Build coverage map: competitorId -> whether they match any keyword
|
|
164
|
+
const coverage = {};
|
|
165
|
+
let matchCount = 0;
|
|
166
|
+
for (const competitor of allCompetitors) {
|
|
167
|
+
const matched = hasFeatureMatch(competitor.capabilities, featureKeywords);
|
|
168
|
+
coverage[competitor.id] = matched;
|
|
169
|
+
if (matched) {
|
|
170
|
+
matchCount++;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Determine positioning
|
|
174
|
+
const positioning = computePositioning(matchCount);
|
|
175
|
+
// Compute Planu-unique capabilities: in PLANU_CAPABILITIES but not in any competitor
|
|
176
|
+
const allCompetitorCaps = allCompetitors.flatMap((c) => c.capabilities.map((cap) => cap.toLowerCase()));
|
|
177
|
+
const uniqueToPlanu = PLANU_CAPABILITIES.filter((cap) => !allCompetitorCaps.some((cc) => cc.includes(cap.toLowerCase())));
|
|
178
|
+
// Gaps: feature keywords not covered by Planu or any competitor
|
|
179
|
+
const planusCapabilitiesLower = PLANU_CAPABILITIES.map((c) => c.toLowerCase());
|
|
180
|
+
const gaps = featureKeywords.filter((kw) => !capabilityMatchesKeyword(planusCapabilitiesLower, kw) &&
|
|
181
|
+
!hasFeatureMatch(allCompetitors.flatMap((c) => c.capabilities), [kw]));
|
|
182
|
+
// Suggestions: derived from weaknesses of competitors that DO have the feature
|
|
183
|
+
const matchingCompetitors = allCompetitors.filter((c) => coverage[c.id] === true);
|
|
184
|
+
const suggestionSet = new Set();
|
|
185
|
+
for (const competitor of matchingCompetitors) {
|
|
186
|
+
for (const weakness of competitor.weaknesses) {
|
|
187
|
+
suggestionSet.add(`Differentiate from ${competitor.name}: ${weakness}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const suggestions = [...suggestionSet];
|
|
191
|
+
// Summary
|
|
192
|
+
const competitorNames = matchingCompetitors.map((c) => c.name);
|
|
193
|
+
const positioningLabel = positioning === 'differentiator'
|
|
194
|
+
? 'a strong differentiator'
|
|
195
|
+
: positioning === 'parity'
|
|
196
|
+
? 'at parity'
|
|
197
|
+
: 'potentially lagging';
|
|
198
|
+
const summary = `Feature analysis: Planu is ${positioningLabel} in this area. ` +
|
|
199
|
+
(competitorNames.length > 0
|
|
200
|
+
? `Competitors with similar capabilities: ${competitorNames.join(', ')}. `
|
|
201
|
+
: 'No direct competitor matches found. ') +
|
|
202
|
+
(uniqueToPlanu.length > 0
|
|
203
|
+
? `Planu-unique capabilities: ${uniqueToPlanu.slice(0, 5).join(', ')}${uniqueToPlanu.length > 5 ? ` (+${uniqueToPlanu.length - 5} more)` : ''}.`
|
|
204
|
+
: 'No unique capabilities identified for this query.');
|
|
205
|
+
return {
|
|
206
|
+
positioning,
|
|
207
|
+
coverage,
|
|
208
|
+
uniqueToPlanu,
|
|
209
|
+
gaps,
|
|
210
|
+
suggestions,
|
|
211
|
+
summary,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=gap-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gap-analyzer.js","sourceRoot":"","sources":["../../../src/engine/competitive/gap-analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HookSection } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns HookSection entries specific to the given AI tool.
|
|
4
|
+
* Returns an empty array for unknown / unsupported tools — extensible by
|
|
5
|
+
* adding entries to AI_HOOK_REGISTRY.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getAIHookSections(aiTool: string): HookSection[];
|
|
8
|
+
//# sourceMappingURL=ai-hook-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-hook-templates.d.ts","sourceRoot":"","sources":["../../../src/engine/hook-generator/ai-hook-templates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAyCxD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAG/D"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// engine/hook-generator/ai-hook-templates.ts — AI-tool-specific hook sections (SPEC-416)
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Claude Code — post-spec-create validation hook comment reminder
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const CLAUDE_SECTIONS = [
|
|
6
|
+
{
|
|
7
|
+
id: 'claude-validate-on-commit',
|
|
8
|
+
phase: 'pre-commit',
|
|
9
|
+
stack: 'claude',
|
|
10
|
+
content: '# Claude Code: run planu validate before committing spec changes\n' +
|
|
11
|
+
'# Configure via .claude/settings.json PostToolUse hooks',
|
|
12
|
+
description: 'Claude Code spec validation reminder',
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Kiro — hooks YAML reminder
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const KIRO_SECTIONS = [
|
|
19
|
+
{
|
|
20
|
+
id: 'kiro-spec-sync',
|
|
21
|
+
phase: 'pre-commit',
|
|
22
|
+
stack: 'kiro',
|
|
23
|
+
content: '# Kiro: ensure .kiro/hooks/*.yaml is committed with spec changes',
|
|
24
|
+
description: 'Kiro hooks YAML sync reminder',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Registry
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const AI_HOOK_REGISTRY = {
|
|
31
|
+
claude: CLAUDE_SECTIONS,
|
|
32
|
+
kiro: KIRO_SECTIONS,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Returns HookSection entries specific to the given AI tool.
|
|
36
|
+
* Returns an empty array for unknown / unsupported tools — extensible by
|
|
37
|
+
* adding entries to AI_HOOK_REGISTRY.
|
|
38
|
+
*/
|
|
39
|
+
export function getAIHookSections(aiTool) {
|
|
40
|
+
const sections = AI_HOOK_REGISTRY[aiTool];
|
|
41
|
+
return sections !== undefined ? [...sections] : [];
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ai-hook-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-hook-templates.js","sourceRoot":"","sources":["../../../src/engine/hook-generator/ai-hook-templates.ts"],"names":[],"mappings":"AAAA,yFAAyF;AAIzF,8EAA8E;AAC9E,kEAAkE;AAClE,8EAA8E;AAE9E,MAAM,eAAe,GAAkB;IACrC;QACE,EAAE,EAAE,2BAA2B;QAC/B,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,QAAQ;QACf,OAAO,EACL,oEAAoE;YACpE,yDAAyD;QAC3D,WAAW,EAAE,sCAAsC;KACpD;CACF,CAAC;AAEF,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E,MAAM,aAAa,GAAkB;IACnC;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,kEAAkE;QAC3E,WAAW,EAAE,+BAA+B;KAC7C;CACF,CAAC;AAEF,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,MAAM,gBAAgB,GAAkC;IACtD,MAAM,EAAE,eAAe;IACvB,IAAI,EAAE,aAAa;CACpB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC1C,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HookSection, MergeResult, HookPreview } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Previews what mergeHooks would do WITHOUT writing any files.
|
|
4
|
+
*/
|
|
5
|
+
export declare function previewHooks(projectPath: string, sections: HookSection[]): Promise<HookPreview[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Merges HookSection entries into the appropriate hook files.
|
|
8
|
+
* Uses idempotent markers so manual edits outside markers are preserved.
|
|
9
|
+
*
|
|
10
|
+
* @param dryRun When true, computes results but does NOT write to disk.
|
|
11
|
+
*/
|
|
12
|
+
export declare function mergeHooks(projectPath: string, sections: HookSection[], dryRun: boolean): Promise<MergeResult[]>;
|
|
13
|
+
//# sourceMappingURL=hook-merger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-merger.d.ts","sourceRoot":"","sources":["../../../src/engine/hook-generator/hook-merger.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAa,MAAM,sBAAsB,CAAC;AAuG7F;;GAEG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,WAAW,EAAE,GACtB,OAAO,CAAC,WAAW,EAAE,CAAC,CA+BxB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,WAAW,EAAE,CAAC,CAkCxB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// engine/hook-generator/hook-merger.ts — Idempotent hook file merge engine (SPEC-416)
|
|
2
|
+
import { readFile, writeFile, access, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { markerStart, markerEnd } from './stack-hook-templates.js';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// File path resolution
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the path to the hook file for a given phase.
|
|
10
|
+
* Prefers .husky/ when the directory exists, falls back to .git/hooks/.
|
|
11
|
+
*/
|
|
12
|
+
async function hookFilePath(projectPath, phase) {
|
|
13
|
+
const huskyDir = join(projectPath, '.husky');
|
|
14
|
+
try {
|
|
15
|
+
await access(huskyDir);
|
|
16
|
+
return join(huskyDir, phase);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return join(projectPath, '.git', 'hooks', phase);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// File I/O helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/** Reads an existing hook file. Returns null if it does not exist. */
|
|
26
|
+
async function readHookFile(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
return await readFile(filePath, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Returns a minimal shell script header for new hook files. */
|
|
35
|
+
function shellHeader() {
|
|
36
|
+
return '#!/usr/bin/env sh\n';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Injects a HookSection into existing hook file content using planu markers.
|
|
40
|
+
*
|
|
41
|
+
* - If markers already present with IDENTICAL body → 'skipped'
|
|
42
|
+
* - If markers present with different body → 'updated' (replaces between markers)
|
|
43
|
+
* - If no markers → 'added' (appends block at end)
|
|
44
|
+
*/
|
|
45
|
+
function injectSection(content, section) {
|
|
46
|
+
const start = markerStart(section.id);
|
|
47
|
+
const end = markerEnd(section.id);
|
|
48
|
+
const block = [`\n${start}`, `# ${section.description}`, section.content, end].join('\n');
|
|
49
|
+
const startIdx = content.indexOf(start);
|
|
50
|
+
const endIdx = content.indexOf(end);
|
|
51
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
52
|
+
// Markers exist — extract current body between them
|
|
53
|
+
const currentBlock = content.slice(startIdx, endIdx + end.length);
|
|
54
|
+
if (currentBlock === block.trimStart()) {
|
|
55
|
+
return { content, action: 'skipped' };
|
|
56
|
+
}
|
|
57
|
+
// Replace the existing block
|
|
58
|
+
const updated = content.slice(0, startIdx) + block.trimStart() + content.slice(endIdx + end.length);
|
|
59
|
+
return { content: updated, action: 'updated' };
|
|
60
|
+
}
|
|
61
|
+
// No markers — append
|
|
62
|
+
const sep = content.endsWith('\n') ? '' : '\n';
|
|
63
|
+
return { content: content + sep + block + '\n', action: 'added' };
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Group sections by phase
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
function groupByPhase(sections) {
|
|
69
|
+
const map = new Map();
|
|
70
|
+
for (const section of sections) {
|
|
71
|
+
const existing = map.get(section.phase) ?? [];
|
|
72
|
+
existing.push(section);
|
|
73
|
+
map.set(section.phase, existing);
|
|
74
|
+
}
|
|
75
|
+
return map;
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Public API
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
/**
|
|
81
|
+
* Previews what mergeHooks would do WITHOUT writing any files.
|
|
82
|
+
*/
|
|
83
|
+
export async function previewHooks(projectPath, sections) {
|
|
84
|
+
const grouped = groupByPhase(sections);
|
|
85
|
+
const previews = [];
|
|
86
|
+
for (const [phase, phaseSections] of grouped) {
|
|
87
|
+
const filePath = await hookFilePath(projectPath, phase);
|
|
88
|
+
const currentContent = await readHookFile(filePath);
|
|
89
|
+
const conflicts = [];
|
|
90
|
+
const proposedAdditions = [];
|
|
91
|
+
for (const section of phaseSections) {
|
|
92
|
+
const start = markerStart(section.id);
|
|
93
|
+
if (currentContent?.includes(start) === true) {
|
|
94
|
+
conflicts.push(section.id);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
proposedAdditions.push(section);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
previews.push({
|
|
101
|
+
phase,
|
|
102
|
+
filePath,
|
|
103
|
+
currentContent,
|
|
104
|
+
proposedAdditions,
|
|
105
|
+
wouldModify: proposedAdditions.length > 0 || conflicts.length > 0,
|
|
106
|
+
conflicts,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return previews;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Merges HookSection entries into the appropriate hook files.
|
|
113
|
+
* Uses idempotent markers so manual edits outside markers are preserved.
|
|
114
|
+
*
|
|
115
|
+
* @param dryRun When true, computes results but does NOT write to disk.
|
|
116
|
+
*/
|
|
117
|
+
export async function mergeHooks(projectPath, sections, dryRun) {
|
|
118
|
+
const grouped = groupByPhase(sections);
|
|
119
|
+
const results = [];
|
|
120
|
+
for (const [phase, phaseSections] of grouped) {
|
|
121
|
+
const filePath = await hookFilePath(projectPath, phase);
|
|
122
|
+
const existing = await readHookFile(filePath);
|
|
123
|
+
let content = existing ?? shellHeader();
|
|
124
|
+
const sectionsAdded = [];
|
|
125
|
+
const sectionsUpdated = [];
|
|
126
|
+
const sectionsSkipped = [];
|
|
127
|
+
for (const section of phaseSections) {
|
|
128
|
+
const outcome = injectSection(content, section);
|
|
129
|
+
content = outcome.content;
|
|
130
|
+
if (outcome.action === 'added') {
|
|
131
|
+
sectionsAdded.push(section.id);
|
|
132
|
+
}
|
|
133
|
+
else if (outcome.action === 'updated') {
|
|
134
|
+
sectionsUpdated.push(section.id);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
sectionsSkipped.push(section.id);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!dryRun && (sectionsAdded.length > 0 || sectionsUpdated.length > 0)) {
|
|
141
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
142
|
+
await writeFile(filePath, content, { encoding: 'utf8', mode: 0o755 });
|
|
143
|
+
}
|
|
144
|
+
results.push({ filePath, sectionsAdded, sectionsUpdated, sectionsSkipped, content });
|
|
145
|
+
}
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=hook-merger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-merger.js","sourceRoot":"","sources":["../../../src/engine/hook-generator/hook-merger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HookSection } from '../../types/index.js';
|
|
2
|
+
export declare function markerStart(id: string): string;
|
|
3
|
+
export declare function markerEnd(id: string): string;
|
|
4
|
+
export declare const STACK_HOOK_TEMPLATES: Record<string, HookSection[]>;
|
|
5
|
+
/**
|
|
6
|
+
* Returns all HookSection entries matching the requested stack tokens.
|
|
7
|
+
* Unknown tokens are silently skipped.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getHookSectionsForStack(stack: string[]): HookSection[];
|
|
10
|
+
//# sourceMappingURL=stack-hook-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack-hook-templates.d.ts","sourceRoot":"","sources":["../../../src/engine/hook-generator/stack-hook-templates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMxD,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE5C;AAMD,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAyE9D,CAAC;AAMF;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAStE"}
|