@polymorphism-tech/morph-spec 4.8.19 → 4.10.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/CLAUDE.md +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +44 -55
- package/bin/task-manager.js +133 -20
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +99 -77
- package/framework/agents.json +734 -182
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +12 -5
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +29 -5
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +69 -14
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +3 -7
- package/package.json +2 -7
- package/src/commands/agents/dispatch-agents.js +104 -6
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +34 -51
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +132 -68
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/task/expand.js +100 -0
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +19 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +32 -57
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +336 -0
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +255 -224
- package/src/utils/agents-installer.js +34 -14
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +272 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/index.js +0 -8
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/index.js +0 -10
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/templates/template-validator.js +0 -296
- package/src/core/workflows/index.js +0 -7
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Artifact Trail — Debugging trail system
|
|
3
|
-
*
|
|
4
|
-
* Creates checkpoint artifact directories and saves:
|
|
5
|
-
* - Intermediate files (spec drafts, contract drafts)
|
|
6
|
-
* - Agent reasoning logs
|
|
7
|
-
* - Validator output logs
|
|
8
|
-
* - Failure artifacts on checkpoint failure
|
|
9
|
-
*
|
|
10
|
-
* Structure:
|
|
11
|
-
* .morph/features/{feature}/artifacts/
|
|
12
|
-
* checkpoint-{n}/
|
|
13
|
-
* spec-draft.md
|
|
14
|
-
* contracts-draft.cs
|
|
15
|
-
* agent-reasoning.json
|
|
16
|
-
* validator-output.json
|
|
17
|
-
* checkpoint-{n}/failures/
|
|
18
|
-
* failure-detail.json
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
22
|
-
import { join } from 'path';
|
|
23
|
-
|
|
24
|
-
const BASE_DIR = join(process.cwd(), '.morph/features');
|
|
25
|
-
|
|
26
|
-
// ============================================================================
|
|
27
|
-
// Path Helpers
|
|
28
|
-
// ============================================================================
|
|
29
|
-
|
|
30
|
-
function artifactsDir(feature) {
|
|
31
|
-
return join(BASE_DIR, feature, 'artifacts');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function checkpointDir(feature, checkpointNum) {
|
|
35
|
-
return join(artifactsDir(feature), `checkpoint-${checkpointNum}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function failuresDir(feature, checkpointNum) {
|
|
39
|
-
return join(checkpointDir(feature, checkpointNum), 'failures');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function ensureDir(dir) {
|
|
43
|
-
if (!existsSync(dir)) {
|
|
44
|
-
mkdirSync(dir, { recursive: true });
|
|
45
|
-
}
|
|
46
|
-
return dir;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Checkpoint Artifacts
|
|
51
|
-
// ============================================================================
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Initialize a checkpoint artifact directory
|
|
55
|
-
* @param {string} feature - Feature name
|
|
56
|
-
* @param {number} checkpointNum - Checkpoint number
|
|
57
|
-
* @returns {string} Path to checkpoint directory
|
|
58
|
-
*/
|
|
59
|
-
export function initCheckpointArtifacts(feature, checkpointNum) {
|
|
60
|
-
const dir = checkpointDir(feature, checkpointNum);
|
|
61
|
-
ensureDir(dir);
|
|
62
|
-
|
|
63
|
-
const manifest = {
|
|
64
|
-
feature,
|
|
65
|
-
checkpointNum,
|
|
66
|
-
createdAt: new Date().toISOString(),
|
|
67
|
-
files: []
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
writeFileSync(join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
71
|
-
return dir;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Save an intermediate artifact file to a checkpoint directory
|
|
76
|
-
* @param {string} feature - Feature name
|
|
77
|
-
* @param {number} checkpointNum - Checkpoint number
|
|
78
|
-
* @param {string} filename - File name (e.g., 'spec-draft.md')
|
|
79
|
-
* @param {string} content - File content
|
|
80
|
-
* @returns {string} Full path to saved file
|
|
81
|
-
*/
|
|
82
|
-
export function saveArtifact(feature, checkpointNum, filename, content) {
|
|
83
|
-
const dir = ensureDir(checkpointDir(feature, checkpointNum));
|
|
84
|
-
const filePath = join(dir, filename);
|
|
85
|
-
|
|
86
|
-
writeFileSync(filePath, content, 'utf8');
|
|
87
|
-
|
|
88
|
-
// Update manifest
|
|
89
|
-
const manifestPath = join(dir, 'manifest.json');
|
|
90
|
-
if (existsSync(manifestPath)) {
|
|
91
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
92
|
-
if (!manifest.files.includes(filename)) {
|
|
93
|
-
manifest.files.push(filename);
|
|
94
|
-
manifest.updatedAt = new Date().toISOString();
|
|
95
|
-
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return filePath;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Save agent reasoning log
|
|
104
|
-
* @param {string} feature - Feature name
|
|
105
|
-
* @param {number} checkpointNum - Checkpoint number
|
|
106
|
-
* @param {Object} reasoning - Agent reasoning data
|
|
107
|
-
*/
|
|
108
|
-
export function saveAgentReasoning(feature, checkpointNum, reasoning) {
|
|
109
|
-
const entry = {
|
|
110
|
-
timestamp: new Date().toISOString(),
|
|
111
|
-
...reasoning
|
|
112
|
-
};
|
|
113
|
-
saveArtifact(feature, checkpointNum, 'agent-reasoning.json', JSON.stringify(entry, null, 2));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Save validator output log
|
|
118
|
-
* @param {string} feature - Feature name
|
|
119
|
-
* @param {number} checkpointNum - Checkpoint number
|
|
120
|
-
* @param {Object} validatorOutput - Validator results
|
|
121
|
-
*/
|
|
122
|
-
export function saveValidatorOutput(feature, checkpointNum, validatorOutput) {
|
|
123
|
-
const entry = {
|
|
124
|
-
timestamp: new Date().toISOString(),
|
|
125
|
-
...validatorOutput
|
|
126
|
-
};
|
|
127
|
-
saveArtifact(feature, checkpointNum, 'validator-output.json', JSON.stringify(entry, null, 2));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ============================================================================
|
|
131
|
-
// Failure Artifacts
|
|
132
|
-
// ============================================================================
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Save failure artifacts when a checkpoint fails
|
|
136
|
-
* @param {string} feature - Feature name
|
|
137
|
-
* @param {number} checkpointNum - Checkpoint number
|
|
138
|
-
* @param {Object} failure - Failure details
|
|
139
|
-
* @param {string} failure.reason - Failure reason
|
|
140
|
-
* @param {Array} failure.errors - Array of error objects
|
|
141
|
-
* @param {number} failure.attemptNumber - Which retry attempt (1-3)
|
|
142
|
-
*/
|
|
143
|
-
export function saveFailureArtifacts(feature, checkpointNum, failure) {
|
|
144
|
-
const dir = ensureDir(failuresDir(feature, checkpointNum));
|
|
145
|
-
const filename = `failure-attempt-${failure.attemptNumber || 1}.json`;
|
|
146
|
-
|
|
147
|
-
const entry = {
|
|
148
|
-
feature,
|
|
149
|
-
checkpointNum,
|
|
150
|
-
timestamp: new Date().toISOString(),
|
|
151
|
-
...failure
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
writeFileSync(join(dir, filename), JSON.stringify(entry, null, 2), 'utf8');
|
|
155
|
-
return join(dir, filename);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Get all failure artifacts for a checkpoint
|
|
160
|
-
* @param {string} feature - Feature name
|
|
161
|
-
* @param {number} checkpointNum - Checkpoint number
|
|
162
|
-
* @returns {Array} Array of failure records
|
|
163
|
-
*/
|
|
164
|
-
export function getFailureArtifacts(feature, checkpointNum) {
|
|
165
|
-
const dir = failuresDir(feature, checkpointNum);
|
|
166
|
-
if (!existsSync(dir)) return [];
|
|
167
|
-
|
|
168
|
-
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
169
|
-
|
|
170
|
-
return files.map(f => {
|
|
171
|
-
try {
|
|
172
|
-
return JSON.parse(readFileSync(join(dir, f), 'utf8'));
|
|
173
|
-
} catch {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
}).filter(Boolean);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* List all checkpoints for a feature
|
|
181
|
-
* @param {string} feature - Feature name
|
|
182
|
-
* @returns {Array} Array of checkpoint info objects
|
|
183
|
-
*/
|
|
184
|
-
export function listCheckpointArtifacts(feature) {
|
|
185
|
-
const dir = artifactsDir(feature);
|
|
186
|
-
if (!existsSync(dir)) return [];
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
return readdirSync(dir, { withFileTypes: true })
|
|
190
|
-
.filter(d => d.isDirectory() && d.name.startsWith('checkpoint-'))
|
|
191
|
-
.map(d => {
|
|
192
|
-
const checkpointPath = join(dir, d.name);
|
|
193
|
-
const manifestPath = join(checkpointPath, 'manifest.json');
|
|
194
|
-
if (existsSync(manifestPath)) {
|
|
195
|
-
return JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
196
|
-
}
|
|
197
|
-
return { name: d.name, path: checkpointPath };
|
|
198
|
-
});
|
|
199
|
-
} catch {
|
|
200
|
-
return [];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview ContextSanitizer - Sanitizes project context before sending to LLM
|
|
3
|
-
* @module morph-spec/sanitizer/context-sanitizer
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { minimatch } from 'minimatch';
|
|
7
|
-
import {
|
|
8
|
-
WHITELIST_FILES,
|
|
9
|
-
BLACKLIST_DIRECTORIES,
|
|
10
|
-
BLACKLIST_FILES,
|
|
11
|
-
SECRET_PATTERNS,
|
|
12
|
-
MAX_FILE_SIZE,
|
|
13
|
-
MAX_SUMMARY_LENGTH
|
|
14
|
-
} from './patterns.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {import('../types/index.js').ProjectContext} ProjectContext
|
|
18
|
-
* @typedef {import('../types/index.js').SanitizedContext} SanitizedContext
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* ContextSanitizer - Removes secrets and sensitive data before LLM analysis
|
|
23
|
-
* Implements hybrid whitelist/blacklist approach per ADR-004
|
|
24
|
-
* @class
|
|
25
|
-
*/
|
|
26
|
-
export class ContextSanitizer {
|
|
27
|
-
/**
|
|
28
|
-
* Sanitize project context (remove secrets, truncate files)
|
|
29
|
-
* @param {ProjectContext} context - Raw project context
|
|
30
|
-
* @returns {SanitizedContext}
|
|
31
|
-
*/
|
|
32
|
-
sanitize(context) {
|
|
33
|
-
// Sanitize package.json (remove private fields, redact secrets)
|
|
34
|
-
const sanitizedPackageJson = this.sanitizePackageJson(context.packageJson);
|
|
35
|
-
|
|
36
|
-
// Extract only filenames from .csproj paths (not full content)
|
|
37
|
-
const csprojFilenames = context.csprojFiles.map(path => path.split('/').pop());
|
|
38
|
-
|
|
39
|
-
// Truncate README and CLAUDE.md to first 500 chars
|
|
40
|
-
const readmeSummary = context.readme
|
|
41
|
-
? this.truncateText(context.readme, MAX_SUMMARY_LENGTH)
|
|
42
|
-
: null;
|
|
43
|
-
|
|
44
|
-
const claudeMdSummary = context.claudeMd
|
|
45
|
-
? this.truncateText(context.claudeMd, MAX_SUMMARY_LENGTH)
|
|
46
|
-
: null;
|
|
47
|
-
|
|
48
|
-
// Sanitize infrastructure summary (remove secrets, just count files)
|
|
49
|
-
const infraSummary = {
|
|
50
|
-
dockerfilesCount: context.infraFiles.dockerfiles.length,
|
|
51
|
-
dockerComposeCount: context.infraFiles.dockerComposeFiles.length,
|
|
52
|
-
bicepFilesCount: context.infraFiles.bicepFiles.length,
|
|
53
|
-
pipelinesCount: context.infraFiles.pipelines.length,
|
|
54
|
-
hasAzure: context.infraFiles.hasAzure,
|
|
55
|
-
hasDocker: context.infraFiles.hasDocker,
|
|
56
|
-
hasDevOps: context.infraFiles.hasDevOps
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Extract git remote domain only (not full URL with credentials)
|
|
60
|
-
const gitRemoteDomain = context.gitRemote
|
|
61
|
-
? this.extractDomain(context.gitRemote)
|
|
62
|
-
: null;
|
|
63
|
-
|
|
64
|
-
// Estimate total files (approximate)
|
|
65
|
-
const totalFiles = context.structure.topLevelDirs.length * 10; // rough estimate
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
packageJson: sanitizedPackageJson,
|
|
69
|
-
csprojFilenames,
|
|
70
|
-
hasSolution: context.solutionFile !== null,
|
|
71
|
-
readmeSummary,
|
|
72
|
-
claudeMdSummary,
|
|
73
|
-
structure: context.structure,
|
|
74
|
-
infraSummary,
|
|
75
|
-
gitRemoteDomain,
|
|
76
|
-
totalFiles
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Sanitize package.json - remove private fields and redact secrets
|
|
82
|
-
* @param {Object|null} packageJson - Raw package.json
|
|
83
|
-
* @returns {Object}
|
|
84
|
-
*/
|
|
85
|
-
sanitizePackageJson(packageJson) {
|
|
86
|
-
if (!packageJson) {
|
|
87
|
-
return {};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Keep only safe fields
|
|
91
|
-
const safe = {
|
|
92
|
-
name: packageJson.name || 'unknown',
|
|
93
|
-
version: packageJson.version,
|
|
94
|
-
description: packageJson.description,
|
|
95
|
-
type: packageJson.type,
|
|
96
|
-
engines: packageJson.engines
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Include dependencies (names only, no versions for simplicity)
|
|
100
|
-
if (packageJson.dependencies) {
|
|
101
|
-
safe.dependencies = Object.keys(packageJson.dependencies);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (packageJson.devDependencies) {
|
|
105
|
-
safe.devDependencies = Object.keys(packageJson.devDependencies);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Include scripts (but redact any that might contain secrets)
|
|
109
|
-
if (packageJson.scripts) {
|
|
110
|
-
safe.scripts = {};
|
|
111
|
-
for (const [key, value] of Object.entries(packageJson.scripts)) {
|
|
112
|
-
safe.scripts[key] = this.redactSecrets(value);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return safe;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Detect and redact secrets from text
|
|
121
|
-
* @param {string} text - Text to sanitize
|
|
122
|
-
* @returns {string} Sanitized text
|
|
123
|
-
*/
|
|
124
|
-
redactSecrets(text) {
|
|
125
|
-
if (!text || typeof text !== 'string') {
|
|
126
|
-
return text;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
let sanitized = text;
|
|
130
|
-
|
|
131
|
-
// Apply all secret patterns
|
|
132
|
-
for (const pattern of SECRET_PATTERNS) {
|
|
133
|
-
sanitized = sanitized.replace(pattern.regex, pattern.replacement);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return sanitized;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Check if file should be excluded from LLM analysis
|
|
141
|
-
* @param {string} filepath - File path (relative or absolute)
|
|
142
|
-
* @returns {boolean} True if file should be excluded
|
|
143
|
-
*/
|
|
144
|
-
shouldExcludeFile(filepath) {
|
|
145
|
-
const normalizedPath = filepath.replace(/\\/g, '/');
|
|
146
|
-
|
|
147
|
-
// Check if path contains blacklisted directory
|
|
148
|
-
for (const blacklistDir of BLACKLIST_DIRECTORIES) {
|
|
149
|
-
if (normalizedPath.includes(`/${blacklistDir}/`) || normalizedPath.startsWith(`${blacklistDir}/`)) {
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Check if filename matches blacklist pattern
|
|
155
|
-
const filename = normalizedPath.split('/').pop();
|
|
156
|
-
for (const pattern of BLACKLIST_FILES) {
|
|
157
|
-
if (minimatch(filename, pattern, { nocase: true })) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Check if file is whitelisted for reading
|
|
167
|
-
* @param {string} filepath - File path
|
|
168
|
-
* @returns {boolean} True if file is whitelisted
|
|
169
|
-
*/
|
|
170
|
-
isWhitelisted(filepath) {
|
|
171
|
-
const normalizedPath = filepath.replace(/\\/g, '/');
|
|
172
|
-
const filename = normalizedPath.split('/').pop();
|
|
173
|
-
|
|
174
|
-
for (const pattern of WHITELIST_FILES) {
|
|
175
|
-
if (minimatch(filename, pattern, { nocase: true })) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
// Also check full path for patterns like ".github/workflows/*.yml"
|
|
179
|
-
if (minimatch(normalizedPath, pattern, { nocase: true })) {
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Truncate text to maximum length
|
|
189
|
-
* @param {string} text - Text to truncate
|
|
190
|
-
* @param {number} maxLength - Maximum length
|
|
191
|
-
* @returns {string} Truncated text
|
|
192
|
-
*/
|
|
193
|
-
truncateText(text, maxLength) {
|
|
194
|
-
if (!text || text.length <= maxLength) {
|
|
195
|
-
return text;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return text.substring(0, maxLength) + '... [truncated]';
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Extract domain from git remote URL
|
|
203
|
-
* @param {string} url - Git remote URL
|
|
204
|
-
* @returns {string|null} Domain only (e.g., "github.com")
|
|
205
|
-
*/
|
|
206
|
-
extractDomain(url) {
|
|
207
|
-
try {
|
|
208
|
-
// Handle SSH format: git@github.com:user/repo.git
|
|
209
|
-
if (url.startsWith('git@')) {
|
|
210
|
-
const match = url.match(/git@([^:]+):/);
|
|
211
|
-
return match ? match[1] : null;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Handle HTTPS format: https://github.com/user/repo.git
|
|
215
|
-
const urlObj = new URL(url);
|
|
216
|
-
return urlObj.hostname;
|
|
217
|
-
} catch (error) {
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Sanitization patterns - Whitelists, Blacklists, Secret patterns
|
|
3
|
-
* @module morph-spec/sanitizer/patterns
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Whitelist: Files explicitly allowed to be read and sent to LLM
|
|
8
|
-
*/
|
|
9
|
-
export const WHITELIST_FILES = [
|
|
10
|
-
'package.json',
|
|
11
|
-
'package-lock.json',
|
|
12
|
-
'yarn.lock',
|
|
13
|
-
'pnpm-lock.yaml',
|
|
14
|
-
'*.csproj',
|
|
15
|
-
'*.sln',
|
|
16
|
-
'README.md',
|
|
17
|
-
'CLAUDE.md',
|
|
18
|
-
'Dockerfile',
|
|
19
|
-
'docker-compose.yml',
|
|
20
|
-
'docker-compose.*.yml',
|
|
21
|
-
'*.bicep',
|
|
22
|
-
'azure-pipelines.yml',
|
|
23
|
-
'.github/workflows/*.yml',
|
|
24
|
-
'.gitlab-ci.yml',
|
|
25
|
-
'tsconfig.json',
|
|
26
|
-
'jsconfig.json',
|
|
27
|
-
'.eslintrc*',
|
|
28
|
-
'.prettierrc*',
|
|
29
|
-
'vite.config.*',
|
|
30
|
-
'next.config.*',
|
|
31
|
-
'nuxt.config.*'
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Blacklist: Directories to NEVER scan or read
|
|
36
|
-
*/
|
|
37
|
-
export const BLACKLIST_DIRECTORIES = [
|
|
38
|
-
'node_modules',
|
|
39
|
-
'.git',
|
|
40
|
-
'.svn',
|
|
41
|
-
'.hg',
|
|
42
|
-
'bin',
|
|
43
|
-
'obj',
|
|
44
|
-
'dist',
|
|
45
|
-
'build',
|
|
46
|
-
'out',
|
|
47
|
-
'.next',
|
|
48
|
-
'.nuxt',
|
|
49
|
-
'coverage',
|
|
50
|
-
'__pycache__',
|
|
51
|
-
'.pytest_cache',
|
|
52
|
-
'venv',
|
|
53
|
-
'env',
|
|
54
|
-
'.venv',
|
|
55
|
-
'.env.*',
|
|
56
|
-
'temp',
|
|
57
|
-
'tmp',
|
|
58
|
-
'.cache'
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Blacklist: File patterns to NEVER read (even if matched by whitelist)
|
|
63
|
-
*/
|
|
64
|
-
export const BLACKLIST_FILES = [
|
|
65
|
-
'.env',
|
|
66
|
-
'.env.local',
|
|
67
|
-
'.env.*.local',
|
|
68
|
-
'.env.production',
|
|
69
|
-
'*.key',
|
|
70
|
-
'*.pem',
|
|
71
|
-
'*.pfx',
|
|
72
|
-
'*.p12',
|
|
73
|
-
'*.cert',
|
|
74
|
-
'*.crt',
|
|
75
|
-
'*secret*',
|
|
76
|
-
'*password*',
|
|
77
|
-
'*credentials*',
|
|
78
|
-
'*token*',
|
|
79
|
-
'secrets.json',
|
|
80
|
-
'appsettings.Production.json',
|
|
81
|
-
'appsettings.Staging.json',
|
|
82
|
-
'*.log',
|
|
83
|
-
'*.sqlite',
|
|
84
|
-
'*.db'
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Secret patterns to redact from file contents
|
|
89
|
-
* Each pattern has: regex, replacement text, description
|
|
90
|
-
*/
|
|
91
|
-
export const SECRET_PATTERNS = [
|
|
92
|
-
{
|
|
93
|
-
name: 'api-key',
|
|
94
|
-
regex: /(?:api[_-]?key|apikey|api[_-]?secret)[\s:=]+["']?([a-zA-Z0-9_\-]{20,})["']?/gi,
|
|
95
|
-
replacement: 'API_KEY=***REDACTED***',
|
|
96
|
-
description: 'API keys'
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: 'bearer-token',
|
|
100
|
-
regex: /(?:bearer|authorization)[\s:=]+["']?([a-zA-Z0-9_\-\.]{20,})["']?/gi,
|
|
101
|
-
replacement: 'Bearer ***REDACTED***',
|
|
102
|
-
description: 'Bearer tokens'
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: 'connection-string',
|
|
106
|
-
regex: /(?:connection[_-]?string|connectionstring)[\s:=]+["']?([^"'\n]{30,})["']?/gi,
|
|
107
|
-
replacement: 'ConnectionString=***REDACTED***',
|
|
108
|
-
description: 'Connection strings'
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: 'sql-password',
|
|
112
|
-
regex: /(?:password|pwd)=([^;"\s]{6,})/gi,
|
|
113
|
-
replacement: 'Password=***REDACTED***',
|
|
114
|
-
description: 'SQL passwords in connection strings'
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: 'azure-key',
|
|
118
|
-
regex: /(?:account[_-]?key|accountkey)[\s:=]+["']?([a-zA-Z0-9+/=]{40,})["']?/gi,
|
|
119
|
-
replacement: 'AccountKey=***REDACTED***',
|
|
120
|
-
description: 'Azure Storage Account Keys'
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
name: 'jwt-secret',
|
|
124
|
-
regex: /(?:jwt[_-]?secret|jwtsecret)[\s:=]+["']?([a-zA-Z0-9_\-]{16,})["']?/gi,
|
|
125
|
-
replacement: 'JwtSecret=***REDACTED***',
|
|
126
|
-
description: 'JWT secrets'
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: 'private-key',
|
|
130
|
-
regex: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----[\s\S]+?-----END (?:RSA |EC )?PRIVATE KEY-----/gi,
|
|
131
|
-
replacement: '-----BEGIN PRIVATE KEY-----\n***REDACTED***\n-----END PRIVATE KEY-----',
|
|
132
|
-
description: 'Private keys (PEM format)'
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: 'github-token',
|
|
136
|
-
regex: /gh[pousr]_[a-zA-Z0-9]{36,}/gi,
|
|
137
|
-
replacement: 'ghp_***REDACTED***',
|
|
138
|
-
description: 'GitHub personal access tokens'
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: 'npm-token',
|
|
142
|
-
regex: /npm_[a-zA-Z0-9]{36,}/gi,
|
|
143
|
-
replacement: 'npm_***REDACTED***',
|
|
144
|
-
description: 'NPM tokens'
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: 'generic-secret',
|
|
148
|
-
regex: /(?:secret|password|pwd|pass|token)[\s:=]+["']([^"'\n]{8,})["']/gi,
|
|
149
|
-
replacement: 'secret=***REDACTED***',
|
|
150
|
-
description: 'Generic secrets'
|
|
151
|
-
}
|
|
152
|
-
];
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Maximum file size to include (50KB)
|
|
156
|
-
* Files larger than this are truncated with a summary
|
|
157
|
-
*/
|
|
158
|
-
export const MAX_FILE_SIZE = 50 * 1024; // 50KB
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Maximum content length for README/CLAUDE.md summaries
|
|
162
|
-
*/
|
|
163
|
-
export const MAX_SUMMARY_LENGTH = 500; // 500 chars
|