@chongyan/autospec 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +472 -0
- package/README.md +476 -0
- package/bin/autospec.js +3 -0
- package/knowledge/README.md +144 -0
- package/knowledge/checklists/code.md +182 -0
- package/knowledge/checklists/design.md +196 -0
- package/knowledge/checklists/release.md +70 -0
- package/knowledge/checklists/requirement.md +169 -0
- package/knowledge/checklists/test.md +46 -0
- package/knowledge/config/README.en.md +44 -0
- package/knowledge/config/README.md +44 -0
- package/knowledge/config/role-composition.yaml +98 -0
- package/knowledge/config/role-extensions.yaml +140 -0
- package/knowledge/config/skill-compositions.yaml +142 -0
- package/knowledge/config/team-stage.yaml +95 -0
- package/knowledge/config/team-tasks.yaml +139 -0
- package/knowledge/config/team-triggers.yaml +198 -0
- package/knowledge/config/validation-patterns.yaml +137 -0
- package/knowledge/domain/README.md +115 -0
- package/knowledge/domain/flows/README.md +194 -0
- package/knowledge/domain/glossary.md +143 -0
- package/knowledge/domain/rules.md +138 -0
- package/knowledge/environment/README.en.md +36 -0
- package/knowledge/environment/README.md +87 -0
- package/knowledge/environment/component-knowledge.md +316 -0
- package/knowledge/environment/detection-patterns.yaml +502 -0
- package/knowledge/environment/middleware-knowledge.md +237 -0
- package/knowledge/environment/template-registry.md +321 -0
- package/knowledge/guides/domain-driven-design.md +345 -0
- package/knowledge/guides/knowledge-management.md +369 -0
- package/knowledge/guides/requirement-engineering.md +329 -0
- package/knowledge/guides/stages/ai-effect-evaluator.md +93 -0
- package/knowledge/guides/stages/code-implementer.md +205 -0
- package/knowledge/guides/stages/code-reviewer.md +111 -0
- package/knowledge/guides/stages/consistency-checker.md +177 -0
- package/knowledge/guides/stages/design-planner.md +401 -0
- package/knowledge/guides/stages/design-reviewer.md +83 -0
- package/knowledge/guides/stages/integration-test-runner.md +105 -0
- package/knowledge/guides/stages/release-checker.md +205 -0
- package/knowledge/guides/stages/requirement-analyzer.md +195 -0
- package/knowledge/guides/stages/requirement-reviewer.md +83 -0
- package/knowledge/guides/stages/security-reviewer.md +89 -0
- package/knowledge/guides/stages/test-context-analyzer.md +250 -0
- package/knowledge/guides/stages/test-generator.md +241 -0
- package/knowledge/guides/stages/test-planner.md +183 -0
- package/knowledge/guides/stages/test-reviewer.md +76 -0
- package/knowledge/guides/stages/unit-test-runner.md +83 -0
- package/knowledge/guides/support/ai-agent-analyzer.md +362 -0
- package/knowledge/guides/support/ai-anomaly-analyzer.md +213 -0
- package/knowledge/guides/support/ai-artifact-evaluator.md +192 -0
- package/knowledge/guides/support/ai-capability-analyzer.md +193 -0
- package/knowledge/guides/support/ai-component-analyzer.md +169 -0
- package/knowledge/guides/support/ai-data-validator.md +276 -0
- package/knowledge/guides/support/ai-evaluation-planner.md +374 -0
- package/knowledge/guides/support/ai-path-evaluator.md +274 -0
- package/knowledge/guides/support/ai-pipeline-evaluator.md +219 -0
- package/knowledge/guides/support/ai-rag-analyzer.md +339 -0
- package/knowledge/guides/support/ai-task-assessor.md +418 -0
- package/knowledge/guides/support/ai-test-diagnostics.md +133 -0
- package/knowledge/guides/support/complexity-assessor.md +268 -0
- package/knowledge/guides/support/component-discovery.md +183 -0
- package/knowledge/guides/support/environment-scanner.md +207 -0
- package/knowledge/guides/support/environment-validator.md +207 -0
- package/knowledge/guides/support/knowledge-generator.md +234 -0
- package/knowledge/guides/support/methodology-extractor.md +55 -0
- package/knowledge/guides/support/pipeline-protocol.md +438 -0
- package/knowledge/guides/support/practice-logger.md +359 -0
- package/knowledge/guides/support/scope-inference.md +174 -0
- package/knowledge/guides/support/skill-distiller.md +91 -0
- package/knowledge/guides/support/skill-updater.md +45 -0
- package/knowledge/guides/support/skill-validator.md +72 -0
- package/knowledge/guides/support/team-orchestrator.md +323 -0
- package/knowledge/guides/support/tech-stack-analyzer.md +139 -0
- package/knowledge/guides/support/test-runner.md +254 -0
- package/knowledge/guides/system-design.md +352 -0
- package/knowledge/organization/ai-native-team.md +318 -0
- package/knowledge/organization/team-metrics.md +228 -0
- package/knowledge/principles/constitution.md +134 -0
- package/knowledge/principles/core-principles.md +368 -0
- package/knowledge/principles/design-philosophy.md +877 -0
- package/knowledge/principles/evolution.md +553 -0
- package/knowledge/process/01-requirement.md +113 -0
- package/knowledge/process/02-design.md +123 -0
- package/knowledge/process/03-implementation.md +90 -0
- package/knowledge/process/04-review.md +80 -0
- package/knowledge/process/05-testing.md +90 -0
- package/knowledge/process/06-delivery.md +88 -0
- package/knowledge/process/README.en.md +38 -0
- package/knowledge/process/README.md +48 -0
- package/knowledge/process/ai-sdlc.md +475 -0
- package/knowledge/process/overview.md +319 -0
- package/knowledge/standards/code-review.md +876 -0
- package/knowledge/standards/coding-style.md +940 -0
- package/knowledge/standards/data-consistency.md +1085 -0
- package/knowledge/standards/document-versioning.md +210 -0
- package/knowledge/standards/risk-detection.md +186 -0
- package/knowledge/templates/ai-evaluation.md +150 -0
- package/knowledge/templates/api-design.md +117 -0
- package/knowledge/templates/database-design.md +132 -0
- package/knowledge/templates/domain-driven-design.md +321 -0
- package/knowledge/templates/product-proposal.md +201 -0
- package/knowledge/templates/system-design.md +227 -0
- package/knowledge/templates/task-breakdown.md +107 -0
- package/knowledge/templates/test-case.md +170 -0
- package/package.json +53 -0
- package/plugins/.claude-plugin/plugin.json +134 -0
- package/plugins/agents/roles/ai-engineer.md +129 -0
- package/plugins/agents/roles/backend-engineer.md +165 -0
- package/plugins/agents/roles/ceo.md +94 -0
- package/plugins/agents/roles/data-engineer.md +135 -0
- package/plugins/agents/roles/devops-engineer.md +181 -0
- package/plugins/agents/roles/frontend-engineer.md +129 -0
- package/plugins/agents/roles/product-owner.md +98 -0
- package/plugins/agents/roles/quality-engineer.md +129 -0
- package/plugins/agents/roles/security-engineer.md +180 -0
- package/plugins/agents/roles/tech-lead.md +97 -0
- package/plugins/agents/support/blind-comparator.md +88 -0
- package/plugins/agents/support/consistency-checker.md +103 -0
- package/plugins/agents/support/failure-diagnostician.md +141 -0
- package/plugins/agents/support/independent-reviewer.md +80 -0
- package/plugins/agents/support/safety-auditor.md +121 -0
- package/plugins/agents/support/skill-benchmarker.md +86 -0
- package/plugins/agents/support/skill-forger.md +105 -0
- package/plugins/agents/support/stage-gate-evaluator.md +121 -0
- package/plugins/agents/support/test-coverage-reviewer.md +73 -0
- package/plugins/benchmarks/templates/README.md +44 -0
- package/plugins/benchmarks/templates/commands/explore-template.yaml +48 -0
- package/plugins/benchmarks/templates/pipeline/agile-template.yaml +84 -0
- package/plugins/benchmarks/templates/pipeline/waterfall-template.yaml +106 -0
- package/plugins/benchmarks/templates/skills/requirement-analyzer-template.yaml +48 -0
- package/plugins/commands/README.en.md +96 -0
- package/plugins/commands/README.md +96 -0
- package/plugins/commands/apply.md +191 -0
- package/plugins/commands/archive.md +76 -0
- package/plugins/commands/env-export.md +79 -0
- package/plugins/commands/env-sync.md +640 -0
- package/plugins/commands/env-template.md +223 -0
- package/plugins/commands/env-update.md +264 -0
- package/plugins/commands/env-validate.md +176 -0
- package/plugins/commands/env.md +79 -0
- package/plugins/commands/explore.md +76 -0
- package/plugins/commands/field-evolve.md +536 -0
- package/plugins/commands/memory.md +249 -0
- package/plugins/commands/project-evolve.md +821 -0
- package/plugins/commands/propose.md +93 -0
- package/plugins/commands/review.md +140 -0
- package/plugins/commands/run.md +224 -0
- package/plugins/commands/status.md +62 -0
- package/plugins/commands/validate.md +108 -0
- package/plugins/hooks/README.en.md +56 -0
- package/plugins/hooks/README.md +56 -0
- package/plugins/hooks/ai-project-guard.js +329 -0
- package/plugins/hooks/artifact-evaluation-hook.js +237 -0
- package/plugins/hooks/constitution-guard.js +211 -0
- package/plugins/hooks/environment-autocommit.js +264 -0
- package/plugins/hooks/environment-manager.js +778 -0
- package/plugins/hooks/execution-tracker.js +354 -0
- package/plugins/hooks/frozen-zone-guard.js +140 -0
- package/plugins/hooks/layer1-validator.js +423 -0
- package/plugins/hooks/lib/artifact-evaluator.js +414 -0
- package/plugins/hooks/lib/benchmarks/change-detector.js +390 -0
- package/plugins/hooks/lib/benchmarks/evaluator.js +605 -0
- package/plugins/hooks/lib/benchmarks/integration-example.js +169 -0
- package/plugins/hooks/lib/data-and-ai-detector.js +275 -0
- package/plugins/hooks/lib/detection-pattern-loader.js +865 -0
- package/plugins/hooks/lib/directory-discovery.js +395 -0
- package/plugins/hooks/lib/environment-config-loader.js +341 -0
- package/plugins/hooks/lib/environment-detector.js +553 -0
- package/plugins/hooks/lib/environment-evolver.js +564 -0
- package/plugins/hooks/lib/environment-registry.js +813 -0
- package/plugins/hooks/lib/execution-path.js +427 -0
- package/plugins/hooks/lib/hook-error-recorder.js +245 -0
- package/plugins/hooks/lib/hook-logger.js +538 -0
- package/plugins/hooks/lib/hook-runner.js +97 -0
- package/plugins/hooks/lib/hook-runner.sh +44 -0
- package/plugins/hooks/lib/hook-state-manager.js +480 -0
- package/plugins/hooks/lib/memory-extractor.js +377 -0
- package/plugins/hooks/lib/memory-manager.js +673 -0
- package/plugins/hooks/lib/metrics-analyzer.js +489 -0
- package/plugins/hooks/lib/project-evolution/auto-fixer.js +511 -0
- package/plugins/hooks/lib/project-evolution/memory-manager.js +346 -0
- package/plugins/hooks/lib/project-evolution/pattern-detector.js +476 -0
- package/plugins/hooks/lib/project-evolution/semantic-indexer.js +480 -0
- package/plugins/hooks/lib/project-structure-detector.js +326 -0
- package/plugins/hooks/lib/rollback-tracker.js +346 -0
- package/plugins/hooks/lib/source-code-scanner.js +596 -0
- package/plugins/hooks/lib/technology-stack-detector.js +374 -0
- package/plugins/hooks/lib/test-failure-analyzer.js +375 -0
- package/plugins/hooks/lib/test-failure-fixer.js +268 -0
- package/plugins/hooks/lib/trace-context.js +277 -0
- package/plugins/hooks/lib/validation-patterns.js +415 -0
- package/plugins/hooks/memory-sync.js +171 -0
- package/plugins/hooks/pipeline-observer.js +413 -0
- package/plugins/hooks/scope-sentinel.js +204 -0
- package/plugins/hooks/trace-initialization.js +169 -0
- package/plugins/memory/templates/code-quality.yaml +149 -0
- package/plugins/memory/templates/multi-system.yaml +155 -0
- package/plugins/memory/templates/team-habits.yaml +119 -0
- package/plugins/memory/templates/testing.yaml +121 -0
- package/plugins/skills/README.en.md +47 -0
- package/plugins/skills/README.md +104 -0
- package/plugins/skills/benchmark-executor/README.md +93 -0
- package/plugins/skills/benchmark-executor/SKILL.md +647 -0
- package/plugins/skills/benchmark-generator/SKILL.md +349 -0
- package/plugins/skills/delivery-stage/SKILL.md +203 -0
- package/plugins/skills/design-stage/SKILL.md +216 -0
- package/plugins/skills/evolution-process/SKILL.md +291 -0
- package/plugins/skills/exploration-phase/SKILL.md +133 -0
- package/plugins/skills/implementation-stage/SKILL.md +179 -0
- package/plugins/skills/layer1-validation/SKILL.md +79 -0
- package/plugins/skills/pending-dashboard/SKILL.md +109 -0
- package/plugins/skills/project-evolution/SKILL.md +847 -0
- package/plugins/skills/requirement-stage/SKILL.md +183 -0
- package/plugins/skills/skill-forge/SKILL.md +223 -0
- package/plugins/skills/skill-forge/references/description-guide.md +92 -0
- package/plugins/skills/skill-forge/references/quality-rubric.md +104 -0
- package/plugins/skills/skill-forge/references/skill-template.md +106 -0
- package/plugins/skills/startup-guard/SKILL.md +38 -0
- package/plugins/skills/testing-stage/SKILL.md +195 -0
- package/scripts/cli/global-init.js +288 -0
- package/scripts/cli/global.js +324 -0
- package/scripts/cli/index.js +55 -0
- package/scripts/cli/init.js +382 -0
- package/scripts/cli/list.js +69 -0
- package/scripts/cli/org.js +340 -0
- package/scripts/cli/update.js +44 -0
- package/scripts/config/commands.config.js +145 -0
- package/scripts/config/hooks.config.js +197 -0
- package/scripts/evolution/evolution-router.js +273 -0
- package/scripts/evolution/evolution-signal-collector.js +307 -0
- package/scripts/evolution/knowledge-loader.js +346 -0
- package/scripts/evolution/marketplace.js +317 -0
- package/scripts/evolution/version-manager.js +371 -0
- package/scripts/install/agents.js +106 -0
- package/scripts/install/commands.js +133 -0
- package/scripts/install/constants.js +424 -0
- package/scripts/install/hook-logger.js +536 -0
- package/scripts/install/hooks.js +110 -0
- package/scripts/install/index.js +39 -0
- package/scripts/install/skills.js +95 -0
- package/scripts/postinstall.js +25 -0
- package/scripts/state.js +376 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills generator for Claude Code integration
|
|
3
|
+
*
|
|
4
|
+
* Generates skill files from templates directory with variable replacement.
|
|
5
|
+
* Source: plugins/skills/{skill-name}/SKILL.md
|
|
6
|
+
* Target: .claude/skills/{skill-name}/SKILL.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
// ESM __dirname equivalent
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
|
|
17
|
+
// Role name mapping (for backward compatibility)
|
|
18
|
+
const ROLE_ALIAS = {
|
|
19
|
+
'backend': 'backend-engineer',
|
|
20
|
+
'frontend': 'frontend-engineer',
|
|
21
|
+
'data': 'data-engineer',
|
|
22
|
+
'ai': 'ai-engineer',
|
|
23
|
+
'quality': 'quality-engineer'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Replace template variables in content
|
|
28
|
+
* @param {string} content - Template content
|
|
29
|
+
* @returns {string} - Content with variables replaced
|
|
30
|
+
*/
|
|
31
|
+
function replaceVariables(content) {
|
|
32
|
+
// Replace ${FW} with .autospec/knowledge (backward compat)
|
|
33
|
+
// Replace ${KNOWLEDGE} with .autospec/knowledge
|
|
34
|
+
return content
|
|
35
|
+
.replace(/\$\{FW\}/g, '.autospec/knowledge')
|
|
36
|
+
.replace(/\$\{KNOWLEDGE\}/g, '.autospec/knowledge');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate all skill files from templates
|
|
41
|
+
* @param {string} claudeDir - Target .claude directory path
|
|
42
|
+
*/
|
|
43
|
+
export function generateSkills(claudeDir) {
|
|
44
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
45
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
46
|
+
|
|
47
|
+
// Get templates directory path (relative to this file)
|
|
48
|
+
// __dirname is scripts/install/, templates are in ../../plugins/skills/
|
|
49
|
+
const templatesDir = path.join(__dirname, '../../plugins/skills');
|
|
50
|
+
|
|
51
|
+
// Check if templates directory exists
|
|
52
|
+
if (!fs.existsSync(templatesDir)) {
|
|
53
|
+
console.warn('Warning: plugins/skills/ directory not found, skipping skill generation');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Get all skill directories
|
|
58
|
+
const skillDirs = fs.readdirSync(templatesDir).filter(f => {
|
|
59
|
+
const skillPath = path.join(templatesDir, f);
|
|
60
|
+
return fs.statSync(skillPath).isDirectory();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Process each skill
|
|
64
|
+
for (const skillName of skillDirs) {
|
|
65
|
+
const skillFile = path.join(templatesDir, skillName, 'SKILL.md');
|
|
66
|
+
if (fs.existsSync(skillFile)) {
|
|
67
|
+
const content = fs.readFileSync(skillFile, 'utf-8');
|
|
68
|
+
const processed = replaceVariables(content);
|
|
69
|
+
|
|
70
|
+
const targetDir = path.join(skillsDir, skillName);
|
|
71
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
72
|
+
fs.writeFileSync(path.join(targetDir, 'SKILL.md'), processed, 'utf-8');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get list of available skill names
|
|
79
|
+
* @returns {string[]} - Array of skill names
|
|
80
|
+
*/
|
|
81
|
+
export function getAvailableSkills() {
|
|
82
|
+
const templatesDir = path.join(__dirname, '../../plugins/skills');
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(templatesDir)) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return fs.readdirSync(templatesDir).filter(f => {
|
|
89
|
+
const skillPath = path.join(templatesDir, f);
|
|
90
|
+
const skillFile = path.join(skillPath, 'SKILL.md');
|
|
91
|
+
return fs.statSync(skillPath).isDirectory() && fs.existsSync(skillFile);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { ROLE_ALIAS };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoSpec postinstall script
|
|
3
|
+
* Shows quick start guide after npm install
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const isGlobal = process.env.npm_config_global === 'true';
|
|
7
|
+
|
|
8
|
+
if (isGlobal) {
|
|
9
|
+
console.log(`
|
|
10
|
+
┌─────────────────────────────────────────────┐
|
|
11
|
+
│ │
|
|
12
|
+
│ AutoSpec installed successfully! │
|
|
13
|
+
│ │
|
|
14
|
+
│ Quick start: │
|
|
15
|
+
│ cd your-project │
|
|
16
|
+
│ autospec init │
|
|
17
|
+
│ │
|
|
18
|
+
│ Then in Claude Code: │
|
|
19
|
+
│ /autospec:run → Full pipeline │
|
|
20
|
+
│ /autospec:run --workflow=hotfix │
|
|
21
|
+
│ /autospec:run --workflow=experiment │
|
|
22
|
+
│ │
|
|
23
|
+
└─────────────────────────────────────────────┘
|
|
24
|
+
`);
|
|
25
|
+
}
|
package/scripts/state.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline state management
|
|
3
|
+
* Tracks which stage the pipeline is at for a given task
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { STATE_FILE, METRICS_FILE, STAGES, WORKFLOWS, AUTOSPEC_DIRS } from './constants.js';
|
|
9
|
+
|
|
10
|
+
const ARCHIVES_DIR = `.autospec/${AUTOSPEC_DIRS.ARCHIVES}`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize or load pipeline state
|
|
14
|
+
*/
|
|
15
|
+
export function loadState(projectDir) {
|
|
16
|
+
const statePath = path.join(projectDir, STATE_FILE);
|
|
17
|
+
if (fs.existsSync(statePath)) {
|
|
18
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Save pipeline state
|
|
25
|
+
*/
|
|
26
|
+
export function saveState(projectDir, state) {
|
|
27
|
+
const statePath = path.join(projectDir, STATE_FILE);
|
|
28
|
+
const dir = path.dirname(statePath);
|
|
29
|
+
if (!fs.existsSync(dir)) {
|
|
30
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
state.updatedAt = new Date().toISOString();
|
|
33
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if there's an active (incomplete) pipeline
|
|
38
|
+
*/
|
|
39
|
+
export function hasActivePipeline(projectDir) {
|
|
40
|
+
const state = loadState(projectDir);
|
|
41
|
+
if (!state) return false;
|
|
42
|
+
return !state.completedAt;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Archive completed pipeline to archives/{spec-id}/
|
|
47
|
+
* Called when a pipeline completes or when starting a new one
|
|
48
|
+
*/
|
|
49
|
+
export function archivePipeline(projectDir, specId) {
|
|
50
|
+
const state = loadState(projectDir);
|
|
51
|
+
if (!state) return false;
|
|
52
|
+
|
|
53
|
+
// Determine specId from state if not provided
|
|
54
|
+
const runId = specId || state.specs || generateRunId(state.taskName, state.createdAt);
|
|
55
|
+
|
|
56
|
+
// Create archives directory
|
|
57
|
+
const archivesDir = path.join(projectDir, ARCHIVES_DIR);
|
|
58
|
+
const archiveDir = path.join(archivesDir, runId);
|
|
59
|
+
if (!fs.existsSync(archiveDir)) {
|
|
60
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Copy state.json to archive
|
|
64
|
+
const archivedStatePath = path.join(archiveDir, 'state.json');
|
|
65
|
+
fs.writeFileSync(archivedStatePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
66
|
+
|
|
67
|
+
// Copy metrics.json to archive if exists
|
|
68
|
+
const metricsPath = path.join(projectDir, METRICS_FILE);
|
|
69
|
+
if (fs.existsSync(metricsPath)) {
|
|
70
|
+
const archivedMetricsPath = path.join(archiveDir, 'metrics.json');
|
|
71
|
+
fs.copyFileSync(metricsPath, archivedMetricsPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Mark state as archived
|
|
75
|
+
state.archivedAt = new Date().toISOString();
|
|
76
|
+
state.archivedTo = runId;
|
|
77
|
+
|
|
78
|
+
return runId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate run ID from task name and timestamp
|
|
83
|
+
*/
|
|
84
|
+
function generateRunId(taskName, createdAt) {
|
|
85
|
+
const timestamp = createdAt ? new Date(createdAt).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10);
|
|
86
|
+
const slug = (taskName || 'unknown')
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-')
|
|
89
|
+
.slice(0, 30);
|
|
90
|
+
return `${timestamp}-${slug}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get list of archived runs
|
|
95
|
+
*/
|
|
96
|
+
export function listArchivedRuns(projectDir) {
|
|
97
|
+
const archivesDir = path.join(projectDir, ARCHIVES_DIR);
|
|
98
|
+
if (!fs.existsSync(archivesDir)) return [];
|
|
99
|
+
|
|
100
|
+
return fs.readdirSync(archivesDir, { withFileTypes: true })
|
|
101
|
+
.filter(dirent => dirent.isDirectory())
|
|
102
|
+
.map(dirent => {
|
|
103
|
+
const archiveDir = path.join(archivesDir, dirent.name);
|
|
104
|
+
const statePath = path.join(archiveDir, 'state.json');
|
|
105
|
+
let runInfo = { id: dirent.name };
|
|
106
|
+
if (fs.existsSync(statePath)) {
|
|
107
|
+
try {
|
|
108
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
109
|
+
runInfo.taskName = state.taskName;
|
|
110
|
+
runInfo.workflow = state.workflow;
|
|
111
|
+
runInfo.completedAt = state.completedAt;
|
|
112
|
+
runInfo.archivedAt = state.archivedAt;
|
|
113
|
+
} catch { /* ignore */ }
|
|
114
|
+
}
|
|
115
|
+
return runInfo;
|
|
116
|
+
})
|
|
117
|
+
.sort((a, b) => (b.archivedAt || '').localeCompare(a.archivedAt || ''));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Load archived run state
|
|
122
|
+
*/
|
|
123
|
+
export function loadArchivedRun(projectDir, runId) {
|
|
124
|
+
const archiveDir = path.join(projectDir, ARCHIVES_DIR, runId);
|
|
125
|
+
const statePath = path.join(archiveDir, 'state.json');
|
|
126
|
+
if (fs.existsSync(statePath)) {
|
|
127
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Standard subsystem types
|
|
134
|
+
*/
|
|
135
|
+
export const SUBSYSTEM_TYPES = {
|
|
136
|
+
BACKEND: 'backend',
|
|
137
|
+
FRONTEND: 'frontend',
|
|
138
|
+
MOBILE: 'mobile',
|
|
139
|
+
DATA: 'data',
|
|
140
|
+
AI: 'ai',
|
|
141
|
+
PIPELINE: 'pipeline'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Labels for subsystem types
|
|
146
|
+
*/
|
|
147
|
+
export const SUBSYSTEM_TYPE_LABELS = {
|
|
148
|
+
[SUBSYSTEM_TYPES.BACKEND]: '后端',
|
|
149
|
+
[SUBSYSTEM_TYPES.FRONTEND]: '前端',
|
|
150
|
+
[SUBSYSTEM_TYPES.MOBILE]: '移动端',
|
|
151
|
+
[SUBSYSTEM_TYPES.DATA]: '数据系统',
|
|
152
|
+
[SUBSYSTEM_TYPES.AI]: 'AI/模型',
|
|
153
|
+
[SUBSYSTEM_TYPES.PIPELINE]: '数据管道'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validate and normalize subsystem names to ensure uniqueness
|
|
158
|
+
* @param {Array} subsystems - subsystems from config
|
|
159
|
+
* @returns {Array} normalized subsystems with unique names
|
|
160
|
+
*/
|
|
161
|
+
export function normalizeSubsystems(subsystems) {
|
|
162
|
+
if (!subsystems || subsystems.length === 0) return [];
|
|
163
|
+
|
|
164
|
+
const nameCount = {};
|
|
165
|
+
const seenNames = new Set();
|
|
166
|
+
|
|
167
|
+
return subsystems.map(sub => {
|
|
168
|
+
let name = sub.name;
|
|
169
|
+
|
|
170
|
+
// Handle duplicate names by adding parent directory suffix
|
|
171
|
+
if (seenNames.has(name)) {
|
|
172
|
+
const parentDir = sub.path.split('/').slice(-2)[0];
|
|
173
|
+
name = `${name}-${parentDir}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Track name occurrences
|
|
177
|
+
nameCount[name] = (nameCount[name] || 0) + 1;
|
|
178
|
+
if (nameCount[name] > 1) {
|
|
179
|
+
name = `${name}-${nameCount[name]}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
seenNames.add(name);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
name,
|
|
186
|
+
type: sub.type || 'unknown',
|
|
187
|
+
path: sub.path
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create new pipeline state for a task
|
|
194
|
+
* @param {string} taskName - task name
|
|
195
|
+
* @param {string} workflow - workflow type
|
|
196
|
+
* @param {Array} roles - involved roles
|
|
197
|
+
* @param {Array} subsystems - optional subsystems config
|
|
198
|
+
*/
|
|
199
|
+
export function createState(taskName, workflow = 'waterfall', roles = [], subsystems = []) {
|
|
200
|
+
const wf = WORKFLOWS[workflow];
|
|
201
|
+
if (!wf) throw new Error(`Unknown workflow: ${workflow}`);
|
|
202
|
+
|
|
203
|
+
// Normalize subsystem names to ensure uniqueness
|
|
204
|
+
const normalizedSubsystems = normalizeSubsystems(subsystems);
|
|
205
|
+
|
|
206
|
+
const stages = {};
|
|
207
|
+
for (const stageId of wf.stages) {
|
|
208
|
+
const stage = STAGES.find(s => s.id === stageId);
|
|
209
|
+
const stageDef = {
|
|
210
|
+
name: stage.name,
|
|
211
|
+
label: stage.label,
|
|
212
|
+
status: 'pending', // pending | in_progress | layer1_pass | layer2_pass | completed | blocked | failed
|
|
213
|
+
layer1: null, // null | pass | fail | blocked
|
|
214
|
+
layer2: null, // null | pass | fail
|
|
215
|
+
retryCount: 0,
|
|
216
|
+
artifacts: [],
|
|
217
|
+
assumptions: [],
|
|
218
|
+
startedAt: null,
|
|
219
|
+
completedAt: null
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Add subsystems array for multi-system projects
|
|
223
|
+
if (normalizedSubsystems.length > 0) {
|
|
224
|
+
stageDef.subsystems = normalizedSubsystems.map(sub => ({
|
|
225
|
+
name: sub.name,
|
|
226
|
+
type: sub.type,
|
|
227
|
+
status: 'pending',
|
|
228
|
+
layer1: null,
|
|
229
|
+
layer2: null,
|
|
230
|
+
retryCount: 0,
|
|
231
|
+
startedAt: null,
|
|
232
|
+
completedAt: null
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
stages[stageId] = stageDef;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
version: '0.1.0',
|
|
241
|
+
taskName,
|
|
242
|
+
workflow,
|
|
243
|
+
roles,
|
|
244
|
+
hasSubsystems: normalizedSubsystems.length > 0,
|
|
245
|
+
subsystemsConfig: normalizedSubsystems.map(s => ({ name: s.name, type: s.type, path: s.path })),
|
|
246
|
+
currentStage: wf.stages[0],
|
|
247
|
+
stages,
|
|
248
|
+
pendingConfirmations: [],
|
|
249
|
+
autoDecisions: [],
|
|
250
|
+
createdAt: new Date().toISOString(),
|
|
251
|
+
updatedAt: new Date().toISOString()
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get status of a specific subsystem in a stage
|
|
257
|
+
* @param {Object} state - pipeline state
|
|
258
|
+
* @param {string} stageId - stage ID (e.g., '03')
|
|
259
|
+
* @param {string} subsystemName - subsystem name
|
|
260
|
+
* @returns {Object|null} subsystem status or null
|
|
261
|
+
*/
|
|
262
|
+
export function getSubsystemStatus(state, stageId, subsystemName) {
|
|
263
|
+
if (!state?.stages?.[stageId]?.subsystems) return null;
|
|
264
|
+
return state.stages[stageId].subsystems.find(s => s.name === subsystemName) || null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Update status of a specific subsystem
|
|
269
|
+
* @param {Object} state - pipeline state
|
|
270
|
+
* @param {string} stageId - stage ID
|
|
271
|
+
* @param {string} subsystemName - subsystem name
|
|
272
|
+
* @param {Object} updates - status updates
|
|
273
|
+
*/
|
|
274
|
+
export function updateSubsystemStatus(state, stageId, subsystemName, updates) {
|
|
275
|
+
if (!state?.stages?.[stageId]?.subsystems) return false;
|
|
276
|
+
|
|
277
|
+
const subsystem = state.stages[stageId].subsystems.find(s => s.name === subsystemName);
|
|
278
|
+
if (!subsystem) return false;
|
|
279
|
+
|
|
280
|
+
Object.assign(subsystem, updates);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Group subsystems by type for display
|
|
286
|
+
* @param {Array} subsystems - subsystem status array
|
|
287
|
+
* @returns {Object} grouped by type
|
|
288
|
+
*/
|
|
289
|
+
function groupSubsystemsByType(subsystems) {
|
|
290
|
+
const groups = {};
|
|
291
|
+
for (const sub of subsystems) {
|
|
292
|
+
const type = sub.type || 'unknown';
|
|
293
|
+
if (!groups[type]) groups[type] = [];
|
|
294
|
+
groups[type].push(sub);
|
|
295
|
+
}
|
|
296
|
+
return groups;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get status icon for display
|
|
301
|
+
*/
|
|
302
|
+
function getStatusIcon(status) {
|
|
303
|
+
return {
|
|
304
|
+
pending: '○',
|
|
305
|
+
in_progress: '◎',
|
|
306
|
+
layer1_pass: '◐',
|
|
307
|
+
layer2_pass: '◑',
|
|
308
|
+
completed: '●',
|
|
309
|
+
blocked: '✖',
|
|
310
|
+
failed: '✖'
|
|
311
|
+
}[status] || '?';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get human-readable status summary
|
|
316
|
+
* Supports both single-system and multi-system modes
|
|
317
|
+
*/
|
|
318
|
+
export function getStatusSummary(state) {
|
|
319
|
+
if (!state) return null;
|
|
320
|
+
|
|
321
|
+
const wf = WORKFLOWS[state.workflow];
|
|
322
|
+
const lines = [];
|
|
323
|
+
lines.push(`Task: ${state.taskName}`);
|
|
324
|
+
lines.push(`Workflow: ${wf.label} (${state.workflow})`);
|
|
325
|
+
lines.push(`Roles: ${state.roles.length > 0 ? state.roles.join(', ') : '(none)'}`);
|
|
326
|
+
lines.push(`Mode: ${state.hasSubsystems ? 'Multi-system' : 'Single-system'}`);
|
|
327
|
+
lines.push(`Current: Stage ${state.currentStage}`);
|
|
328
|
+
lines.push('');
|
|
329
|
+
|
|
330
|
+
// Show subsystems config if present
|
|
331
|
+
if (state.hasSubsystems && state.subsystemsConfig) {
|
|
332
|
+
lines.push('Subsystems:');
|
|
333
|
+
const groups = groupSubsystemsByType(state.subsystemsConfig);
|
|
334
|
+
for (const [type, subs] of Object.entries(groups)) {
|
|
335
|
+
const typeLabel = SUBSYSTEM_TYPE_LABELS[type] || type;
|
|
336
|
+
lines.push(` [${typeLabel}] ${subs.map(s => s.name).join(', ')}`);
|
|
337
|
+
}
|
|
338
|
+
lines.push('');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
lines.push('Stages:');
|
|
342
|
+
|
|
343
|
+
for (const stageId of wf.stages) {
|
|
344
|
+
const s = state.stages[stageId];
|
|
345
|
+
const stage = STAGES.find(st => st.id === stageId);
|
|
346
|
+
const icon = getStatusIcon(s.status);
|
|
347
|
+
const current = stageId === state.currentStage ? ' ←' : '';
|
|
348
|
+
lines.push(` ${icon} [${stageId}] ${stage.label} — ${s.status}${current}`);
|
|
349
|
+
|
|
350
|
+
// Show subsystem details for current stage in multi-system mode
|
|
351
|
+
if (s.subsystems && stageId === state.currentStage) {
|
|
352
|
+
const groups = groupSubsystemsByType(s.subsystems);
|
|
353
|
+
for (const [type, subs] of Object.entries(groups)) {
|
|
354
|
+
const typeLabel = SUBSYSTEM_TYPE_LABELS[type] || type;
|
|
355
|
+
lines.push(` ${typeLabel}:`);
|
|
356
|
+
for (const sub of subs) {
|
|
357
|
+
const subIcon = getStatusIcon(sub.status);
|
|
358
|
+
const l1 = sub.layer1 ? `[L1:${sub.layer1}]` : '';
|
|
359
|
+
const l2 = sub.layer2 ? `[L2:${sub.layer2}]` : '';
|
|
360
|
+
lines.push(` ${subIcon} ${sub.name} ${l1}${l2}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Show pending items count if any
|
|
367
|
+
if (state.pendingConfirmations?.length > 0) {
|
|
368
|
+
lines.push('');
|
|
369
|
+
lines.push(`Pending confirmations: ${state.pendingConfirmations.length}`);
|
|
370
|
+
}
|
|
371
|
+
if (state.autoDecisions?.length > 0) {
|
|
372
|
+
lines.push(`Auto decisions: ${state.autoDecisions.length}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return lines.join('\n');
|
|
376
|
+
}
|