agentsys 5.0.2 → 5.1.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-plugin/marketplace.json +21 -14
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +24 -1
- package/README.md +7 -6
- package/adapters/codex/skills/agnix/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project-agents/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project-github/SKILL.md +0 -1
- package/adapters/codex/skills/consult/SKILL.md +133 -59
- package/adapters/codex/skills/debate/SKILL.md +214 -0
- package/adapters/codex/skills/delivery-approval/SKILL.md +0 -1
- package/adapters/codex/skills/deslop/SKILL.md +0 -1
- package/adapters/codex/skills/drift-detect/SKILL.md +0 -1
- package/adapters/codex/skills/enhance/SKILL.md +0 -1
- package/adapters/codex/skills/learn/SKILL.md +0 -1
- package/adapters/codex/skills/next-task/SKILL.md +0 -1
- package/adapters/codex/skills/perf/SKILL.md +0 -1
- package/adapters/codex/skills/repo-map/SKILL.md +0 -1
- package/adapters/codex/skills/ship/SKILL.md +0 -1
- package/adapters/codex/skills/ship-ci-review-loop/SKILL.md +0 -1
- package/adapters/codex/skills/ship-deployment/SKILL.md +0 -1
- package/adapters/codex/skills/ship-error-handling/SKILL.md +0 -1
- package/adapters/codex/skills/sync-docs/SKILL.md +0 -1
- package/adapters/opencode/agents/agent-enhancer.md +0 -1
- package/adapters/opencode/agents/agnix-agent.md +0 -1
- package/adapters/opencode/agents/ci-fixer.md +0 -1
- package/adapters/opencode/agents/ci-monitor.md +0 -1
- package/adapters/opencode/agents/claudemd-enhancer.md +0 -1
- package/adapters/opencode/agents/consult-agent.md +123 -31
- package/adapters/opencode/agents/cross-file-enhancer.md +0 -1
- package/adapters/opencode/agents/debate-orchestrator.md +169 -0
- package/adapters/opencode/agents/delivery-validator.md +0 -1
- package/adapters/opencode/agents/deslop-agent.md +0 -1
- package/adapters/opencode/agents/docs-enhancer.md +0 -1
- package/adapters/opencode/agents/exploration-agent.md +0 -1
- package/adapters/opencode/agents/hooks-enhancer.md +0 -1
- package/adapters/opencode/agents/implementation-agent.md +0 -1
- package/adapters/opencode/agents/learn-agent.md +0 -1
- package/adapters/opencode/agents/map-validator.md +0 -1
- package/adapters/opencode/agents/perf-analyzer.md +0 -1
- package/adapters/opencode/agents/perf-code-paths.md +0 -1
- package/adapters/opencode/agents/perf-investigation-logger.md +0 -1
- package/adapters/opencode/agents/perf-orchestrator.md +0 -1
- package/adapters/opencode/agents/perf-theory-gatherer.md +0 -1
- package/adapters/opencode/agents/perf-theory-tester.md +0 -1
- package/adapters/opencode/agents/plan-synthesizer.md +0 -1
- package/adapters/opencode/agents/planning-agent.md +0 -1
- package/adapters/opencode/agents/plugin-enhancer.md +0 -1
- package/adapters/opencode/agents/prompt-enhancer.md +0 -1
- package/adapters/opencode/agents/simple-fixer.md +0 -1
- package/adapters/opencode/agents/skills-enhancer.md +0 -1
- package/adapters/opencode/agents/sync-docs-agent.md +0 -1
- package/adapters/opencode/agents/task-discoverer.md +0 -1
- package/adapters/opencode/agents/test-coverage-checker.md +0 -1
- package/adapters/opencode/agents/worktree-manager.md +0 -1
- package/adapters/opencode/commands/agnix.md +0 -1
- package/adapters/opencode/commands/audit-project-agents.md +0 -1
- package/adapters/opencode/commands/audit-project-github.md +0 -1
- package/adapters/opencode/commands/audit-project.md +0 -1
- package/adapters/opencode/commands/consult.md +134 -59
- package/adapters/opencode/commands/debate.md +224 -0
- package/adapters/opencode/commands/delivery-approval.md +0 -1
- package/adapters/opencode/commands/deslop.md +0 -1
- package/adapters/opencode/commands/drift-detect.md +0 -1
- package/adapters/opencode/commands/enhance.md +0 -1
- package/adapters/opencode/commands/learn.md +0 -1
- package/adapters/opencode/commands/next-task.md +0 -1
- package/adapters/opencode/commands/perf.md +0 -1
- package/adapters/opencode/commands/repo-map.md +0 -1
- package/adapters/opencode/commands/ship-ci-review-loop.md +0 -1
- package/adapters/opencode/commands/ship-deployment.md +0 -1
- package/adapters/opencode/commands/ship-error-handling.md +0 -1
- package/adapters/opencode/commands/ship.md +0 -1
- package/adapters/opencode/commands/sync-docs.md +0 -1
- package/adapters/opencode/skills/agnix/SKILL.md +1 -2
- package/adapters/opencode/skills/consult/SKILL.md +41 -27
- package/adapters/opencode/skills/debate/SKILL.md +245 -0
- package/adapters/opencode/skills/deslop/SKILL.md +1 -2
- package/adapters/opencode/skills/discover-tasks/SKILL.md +1 -2
- package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -2
- package/adapters/opencode/skills/learn/SKILL.md +1 -2
- package/adapters/opencode/skills/orchestrate-review/SKILL.md +0 -1
- package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -2
- package/adapters/opencode/skills/repo-mapping/SKILL.md +1 -2
- package/adapters/opencode/skills/sync-docs/SKILL.md +1 -2
- package/adapters/opencode/skills/validate-delivery/SKILL.md +1 -2
- package/lib/adapter-transforms.js +24 -4
- package/package.json +1 -1
- package/plugins/agnix/.claude-plugin/plugin.json +1 -1
- package/plugins/agnix/skills/agnix/SKILL.md +1 -1
- package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
- package/plugins/audit-project/lib/adapter-transforms.js +24 -4
- package/plugins/consult/.claude-plugin/plugin.json +1 -1
- package/plugins/consult/agents/consult-agent.md +123 -30
- package/plugins/consult/commands/consult.md +136 -60
- package/plugins/consult/skills/consult/SKILL.md +39 -24
- package/plugins/debate/.claude-plugin/plugin.json +21 -0
- package/plugins/debate/agents/debate-orchestrator.md +175 -0
- package/plugins/debate/commands/debate.md +221 -0
- package/plugins/debate/lib/adapter-transforms.js +298 -0
- package/plugins/debate/lib/collectors/codebase.js +392 -0
- package/plugins/debate/lib/collectors/docs-patterns.js +713 -0
- package/plugins/debate/lib/collectors/documentation.js +219 -0
- package/plugins/debate/lib/collectors/github.js +330 -0
- package/plugins/debate/lib/collectors/index.js +126 -0
- package/plugins/debate/lib/config/index.js +14 -0
- package/plugins/debate/lib/cross-platform/index.js +539 -0
- package/plugins/debate/lib/discovery/index.js +352 -0
- package/plugins/debate/lib/drift-detect/collectors.js +37 -0
- package/plugins/debate/lib/enhance/agent-analyzer.js +421 -0
- package/plugins/debate/lib/enhance/agent-patterns.js +571 -0
- package/plugins/debate/lib/enhance/auto-suppression.js +622 -0
- package/plugins/debate/lib/enhance/benchmark.js +417 -0
- package/plugins/debate/lib/enhance/cross-file-analyzer.js +930 -0
- package/plugins/debate/lib/enhance/cross-file-patterns.js +370 -0
- package/plugins/debate/lib/enhance/docs-analyzer.js +325 -0
- package/plugins/debate/lib/enhance/docs-patterns.js +671 -0
- package/plugins/debate/lib/enhance/fixer.js +721 -0
- package/plugins/debate/lib/enhance/hook-analyzer.js +135 -0
- package/plugins/debate/lib/enhance/hook-patterns.js +40 -0
- package/plugins/debate/lib/enhance/index.js +127 -0
- package/plugins/debate/lib/enhance/plugin-analyzer.js +402 -0
- package/plugins/debate/lib/enhance/plugin-patterns.js +326 -0
- package/plugins/debate/lib/enhance/projectmemory-analyzer.js +551 -0
- package/plugins/debate/lib/enhance/projectmemory-patterns.js +617 -0
- package/plugins/debate/lib/enhance/prompt-analyzer.js +457 -0
- package/plugins/debate/lib/enhance/prompt-patterns.js +1484 -0
- package/plugins/debate/lib/enhance/reporter.js +1348 -0
- package/plugins/debate/lib/enhance/security-patterns.js +284 -0
- package/plugins/debate/lib/enhance/skill-analyzer.js +182 -0
- package/plugins/debate/lib/enhance/skill-patterns.js +147 -0
- package/plugins/debate/lib/enhance/suppression.js +352 -0
- package/plugins/debate/lib/enhance/tool-patterns.js +373 -0
- package/plugins/debate/lib/index.js +270 -0
- package/plugins/debate/lib/patterns/cli-enhancers.js +611 -0
- package/plugins/debate/lib/patterns/pipeline.js +948 -0
- package/plugins/debate/lib/patterns/review-patterns.js +558 -0
- package/plugins/debate/lib/patterns/slop-analyzers.js +2305 -0
- package/plugins/debate/lib/patterns/slop-patterns.js +1187 -0
- package/plugins/debate/lib/perf/analyzer/index.js +22 -0
- package/plugins/debate/lib/perf/argument-parser.js +105 -0
- package/plugins/debate/lib/perf/baseline-comparator.js +50 -0
- package/plugins/debate/lib/perf/baseline-store.js +127 -0
- package/plugins/debate/lib/perf/benchmark-runner.js +404 -0
- package/plugins/debate/lib/perf/breaking-point-finder.js +52 -0
- package/plugins/debate/lib/perf/breaking-point-runner.js +60 -0
- package/plugins/debate/lib/perf/checkpoint.js +123 -0
- package/plugins/debate/lib/perf/code-paths.js +86 -0
- package/plugins/debate/lib/perf/consolidation.js +37 -0
- package/plugins/debate/lib/perf/constraint-runner.js +71 -0
- package/plugins/debate/lib/perf/experiment-runner.js +32 -0
- package/plugins/debate/lib/perf/index.js +41 -0
- package/plugins/debate/lib/perf/investigation-state.js +874 -0
- package/plugins/debate/lib/perf/optimization-runner.js +79 -0
- package/plugins/debate/lib/perf/profilers/go.js +22 -0
- package/plugins/debate/lib/perf/profilers/index.js +46 -0
- package/plugins/debate/lib/perf/profilers/java.js +23 -0
- package/plugins/debate/lib/perf/profilers/node.js +27 -0
- package/plugins/debate/lib/perf/profilers/python.js +23 -0
- package/plugins/debate/lib/perf/profilers/rust.js +23 -0
- package/plugins/debate/lib/perf/profiling-runner.js +75 -0
- package/plugins/debate/lib/perf/schemas.js +140 -0
- package/plugins/debate/lib/platform/detect-platform.js +413 -0
- package/plugins/debate/lib/platform/detection-configs.js +93 -0
- package/plugins/debate/lib/platform/state-dir.js +132 -0
- package/plugins/debate/lib/platform/verify-tools.js +182 -0
- package/plugins/debate/lib/repo-map/cache.js +152 -0
- package/plugins/debate/lib/repo-map/concurrency.js +29 -0
- package/plugins/debate/lib/repo-map/index.js +222 -0
- package/plugins/debate/lib/repo-map/installer.js +212 -0
- package/plugins/debate/lib/repo-map/queries/go.js +27 -0
- package/plugins/debate/lib/repo-map/queries/index.js +100 -0
- package/plugins/debate/lib/repo-map/queries/java.js +38 -0
- package/plugins/debate/lib/repo-map/queries/javascript.js +55 -0
- package/plugins/debate/lib/repo-map/queries/python.js +24 -0
- package/plugins/debate/lib/repo-map/queries/rust.js +73 -0
- package/plugins/debate/lib/repo-map/queries/typescript.js +38 -0
- package/plugins/debate/lib/repo-map/runner.js +1364 -0
- package/plugins/debate/lib/repo-map/updater.js +562 -0
- package/plugins/debate/lib/repo-map/usage-analyzer.js +407 -0
- package/plugins/debate/lib/schemas/plugin-manifest.schema.json +57 -0
- package/plugins/debate/lib/schemas/validator.js +247 -0
- package/plugins/debate/lib/sources/custom-handler.js +199 -0
- package/plugins/debate/lib/sources/policy-questions.js +246 -0
- package/plugins/debate/lib/sources/source-cache.js +165 -0
- package/plugins/debate/lib/state/workflow-state.js +576 -0
- package/plugins/debate/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/debate/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/debate/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/debate/lib/types/index.d.ts +84 -0
- package/plugins/debate/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/debate/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/debate/lib/utils/atomic-write.js +94 -0
- package/plugins/debate/lib/utils/cache-manager.js +159 -0
- package/plugins/debate/lib/utils/command-parser.js +0 -0
- package/plugins/debate/lib/utils/context-optimizer.js +300 -0
- package/plugins/debate/lib/utils/deprecation.js +37 -0
- package/plugins/debate/lib/utils/shell-escape.js +88 -0
- package/plugins/debate/lib/utils/state-helpers.js +61 -0
- package/plugins/debate/skills/debate/SKILL.md +264 -0
- package/plugins/deslop/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop/lib/adapter-transforms.js +24 -4
- package/plugins/deslop/skills/deslop/SKILL.md +1 -1
- package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
- package/plugins/drift-detect/lib/adapter-transforms.js +24 -4
- package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
- package/plugins/enhance/.claude-plugin/plugin.json +1 -1
- package/plugins/enhance/lib/adapter-transforms.js +24 -4
- package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
- package/plugins/learn/.claude-plugin/plugin.json +1 -1
- package/plugins/learn/agents/learn-agent.md +1 -1
- package/plugins/learn/lib/adapter-transforms.js +24 -4
- package/plugins/learn/skills/learn/SKILL.md +1 -1
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/agents/exploration-agent.md +1 -1
- package/plugins/next-task/lib/adapter-transforms.js +24 -4
- package/plugins/next-task/skills/discover-tasks/SKILL.md +1 -1
- package/plugins/next-task/skills/validate-delivery/SKILL.md +1 -1
- package/plugins/perf/.claude-plugin/plugin.json +1 -1
- package/plugins/perf/lib/adapter-transforms.js +24 -4
- package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
- package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
- package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
- package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
- package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
- package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
- package/plugins/repo-map/lib/adapter-transforms.js +24 -4
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/lib/adapter-transforms.js +24 -4
- package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
- package/plugins/sync-docs/lib/adapter-transforms.js +24 -4
- package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
- package/scripts/gen-adapters.js +6 -7
- package/scripts/generate-docs.js +4 -2
- package/scripts/plugins.txt +1 -0
- package/site/content.json +6 -6
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance investigation state management
|
|
3
|
+
*
|
|
4
|
+
* Stores investigation state and logs under the platform-aware state directory:
|
|
5
|
+
* - {state-dir}/perf/investigation.json
|
|
6
|
+
* - {state-dir}/perf/investigations/{id}.md
|
|
7
|
+
*
|
|
8
|
+
* @module lib/perf/investigation-state
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
const { getStateDir } = require('../platform/state-dir');
|
|
15
|
+
const { validateInvestigationState, assertValid } = require('./schemas');
|
|
16
|
+
const { writeJsonAtomic, writeFileAtomic } = require('../utils/atomic-write');
|
|
17
|
+
const { isPlainObject, updatesApplied, sleepForRetry } = require('../utils/state-helpers');
|
|
18
|
+
|
|
19
|
+
const SCHEMA_VERSION = 1;
|
|
20
|
+
const INVESTIGATION_FILE = 'investigation.json';
|
|
21
|
+
const LOG_DIR = 'investigations';
|
|
22
|
+
const BASELINE_DIR = 'baselines';
|
|
23
|
+
const PHASES = [
|
|
24
|
+
'setup',
|
|
25
|
+
'baseline',
|
|
26
|
+
'breaking-point',
|
|
27
|
+
'constraints',
|
|
28
|
+
'hypotheses',
|
|
29
|
+
'code-paths',
|
|
30
|
+
'profiling',
|
|
31
|
+
'optimization',
|
|
32
|
+
'decision',
|
|
33
|
+
'consolidation'
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate and resolve path to prevent path traversal attacks
|
|
38
|
+
* @param {string} basePath - Base directory path
|
|
39
|
+
* @returns {string} Validated absolute path
|
|
40
|
+
*/
|
|
41
|
+
function validatePath(basePath) {
|
|
42
|
+
if (typeof basePath !== 'string' || basePath.length === 0) {
|
|
43
|
+
throw new Error('Path must be a non-empty string');
|
|
44
|
+
}
|
|
45
|
+
const resolved = path.resolve(basePath);
|
|
46
|
+
if (resolved.includes('\0')) {
|
|
47
|
+
throw new Error('Path contains invalid null byte');
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate that target path is within base directory
|
|
54
|
+
* @param {string} targetPath - Target file path
|
|
55
|
+
* @param {string} basePath - Base directory
|
|
56
|
+
*/
|
|
57
|
+
function validatePathWithinBase(targetPath, basePath) {
|
|
58
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
59
|
+
const resolvedBase = path.resolve(basePath);
|
|
60
|
+
if (!resolvedTarget.startsWith(resolvedBase + path.sep) && resolvedTarget !== resolvedBase) {
|
|
61
|
+
throw new Error('Path traversal detected');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function assertSafeInvestigationId(id) {
|
|
66
|
+
if (!id || typeof id !== 'string') {
|
|
67
|
+
throw new Error('Investigation id is required');
|
|
68
|
+
}
|
|
69
|
+
if (id.includes('..') || id.includes('/') || id.includes('\\') || id.includes('\0')) {
|
|
70
|
+
throw new Error('Investigation id contains invalid characters');
|
|
71
|
+
}
|
|
72
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(id)) {
|
|
73
|
+
throw new Error('Investigation id contains invalid characters');
|
|
74
|
+
}
|
|
75
|
+
return id;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate a unique investigation ID
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function generateInvestigationId() {
|
|
83
|
+
const now = new Date();
|
|
84
|
+
const date = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
85
|
+
const time = now.toISOString().slice(11, 19).replace(/:/g, '');
|
|
86
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
87
|
+
return `perf-${date}-${time}-${random}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get perf state directory path
|
|
92
|
+
* @param {string} basePath
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function getPerfDir(basePath = process.cwd()) {
|
|
96
|
+
const validatedBase = validatePath(basePath);
|
|
97
|
+
const perfDir = path.join(validatedBase, getStateDir(basePath), 'perf');
|
|
98
|
+
validatePathWithinBase(perfDir, validatedBase);
|
|
99
|
+
return perfDir;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Ensure perf directories exist
|
|
104
|
+
* @param {string} basePath
|
|
105
|
+
* @returns {{ perfDir: string, logDir: string, baselineDir: string }}
|
|
106
|
+
*/
|
|
107
|
+
function ensurePerfDirs(basePath = process.cwd()) {
|
|
108
|
+
const perfDir = getPerfDir(basePath);
|
|
109
|
+
const logDir = path.join(perfDir, LOG_DIR);
|
|
110
|
+
const baselineDir = path.join(perfDir, BASELINE_DIR);
|
|
111
|
+
|
|
112
|
+
if (!fs.existsSync(perfDir)) {
|
|
113
|
+
fs.mkdirSync(perfDir, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
if (!fs.existsSync(logDir)) {
|
|
116
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
if (!fs.existsSync(baselineDir)) {
|
|
119
|
+
fs.mkdirSync(baselineDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { perfDir, logDir, baselineDir };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get path to investigation.json
|
|
127
|
+
* @param {string} basePath
|
|
128
|
+
* @returns {string}
|
|
129
|
+
*/
|
|
130
|
+
function getInvestigationPath(basePath = process.cwd()) {
|
|
131
|
+
const perfDir = getPerfDir(basePath);
|
|
132
|
+
return path.join(perfDir, INVESTIGATION_FILE);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get path to investigation log
|
|
137
|
+
* @param {string} id
|
|
138
|
+
* @param {string} basePath
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
function getInvestigationLogPath(id, basePath = process.cwd()) {
|
|
142
|
+
const safeId = assertSafeInvestigationId(id);
|
|
143
|
+
const { logDir } = ensurePerfDirs(basePath);
|
|
144
|
+
return path.join(logDir, `${safeId}.md`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Read investigation.json
|
|
149
|
+
* @param {string} basePath
|
|
150
|
+
* @returns {object|null}
|
|
151
|
+
*/
|
|
152
|
+
function readInvestigation(basePath = process.cwd()) {
|
|
153
|
+
const investigationPath = getInvestigationPath(basePath);
|
|
154
|
+
if (!fs.existsSync(investigationPath)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(fs.readFileSync(investigationPath, 'utf8'));
|
|
159
|
+
const validation = validateInvestigationState(parsed);
|
|
160
|
+
if (!validation.ok) {
|
|
161
|
+
console.error(`[CRITICAL] Invalid investigation state at ${investigationPath}: ${validation.errors.join(', ')}`);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return parsed;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(`[CRITICAL] Corrupted investigation.json at ${investigationPath}: ${error.message}`);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Write investigation.json
|
|
173
|
+
* Increments version for optimistic locking
|
|
174
|
+
* @param {object} state
|
|
175
|
+
* @param {string} basePath
|
|
176
|
+
* @returns {boolean}
|
|
177
|
+
*/
|
|
178
|
+
function writeInvestigation(state, basePath = process.cwd()) {
|
|
179
|
+
ensurePerfDirs(basePath);
|
|
180
|
+
const investigationPath = getInvestigationPath(basePath);
|
|
181
|
+
const nextState = {
|
|
182
|
+
...state,
|
|
183
|
+
updatedAt: new Date().toISOString(),
|
|
184
|
+
_version: (state._version || 0) + 1
|
|
185
|
+
};
|
|
186
|
+
assertValid(validateInvestigationState(nextState), 'Invalid investigation state');
|
|
187
|
+
writeJsonAtomic(investigationPath, nextState);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Update investigation.json with partial updates
|
|
193
|
+
* Uses optimistic locking with version check and retry
|
|
194
|
+
* @param {object} updates
|
|
195
|
+
* @param {string} basePath
|
|
196
|
+
* @returns {object|null}
|
|
197
|
+
*/
|
|
198
|
+
function updateInvestigation(updates, basePath = process.cwd()) {
|
|
199
|
+
const MAX_RETRIES = 5;
|
|
200
|
+
let fallbackState = null;
|
|
201
|
+
|
|
202
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
203
|
+
const current = readInvestigation(basePath) || {};
|
|
204
|
+
fallbackState = current;
|
|
205
|
+
const initialVersion = current._version || 0;
|
|
206
|
+
const nextState = { ...current };
|
|
207
|
+
|
|
208
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
209
|
+
// Skip internal version field from updates
|
|
210
|
+
if (key === '_version') continue;
|
|
211
|
+
|
|
212
|
+
if (value === null) {
|
|
213
|
+
nextState[key] = null;
|
|
214
|
+
} else if (isPlainObject(value) && isPlainObject(nextState[key])) {
|
|
215
|
+
nextState[key] = { ...nextState[key], ...value };
|
|
216
|
+
} else {
|
|
217
|
+
nextState[key] = value;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Preserve version for write (writeInvestigation will increment it)
|
|
222
|
+
nextState._version = initialVersion;
|
|
223
|
+
|
|
224
|
+
writeInvestigation(nextState, basePath);
|
|
225
|
+
|
|
226
|
+
// Re-read to verify our write succeeded
|
|
227
|
+
const afterWrite = readInvestigation(basePath);
|
|
228
|
+
if (afterWrite) {
|
|
229
|
+
fallbackState = afterWrite;
|
|
230
|
+
}
|
|
231
|
+
if (afterWrite && afterWrite._version >= initialVersion + 1 && updatesApplied(afterWrite, updates)) {
|
|
232
|
+
return afterWrite; // Success
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Version conflict - retry after brief delay
|
|
236
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
237
|
+
const delay = Math.floor(Math.random() * 50) + 10;
|
|
238
|
+
sleepForRetry(delay);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// All retries exhausted
|
|
243
|
+
console.error('[ERROR] updateInvestigation: failed to apply updates after max retries');
|
|
244
|
+
return readInvestigation(basePath) || fallbackState || { ...updates };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Initialize a new investigation
|
|
249
|
+
* @param {object} options
|
|
250
|
+
* @param {string} basePath
|
|
251
|
+
* @returns {object}
|
|
252
|
+
*/
|
|
253
|
+
function initializeInvestigation(options = {}, basePath = process.cwd()) {
|
|
254
|
+
const id = options.id || generateInvestigationId();
|
|
255
|
+
const phase = options.phase || PHASES[0];
|
|
256
|
+
|
|
257
|
+
if (!PHASES.includes(phase)) {
|
|
258
|
+
throw new Error(`Invalid perf phase: ${phase}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const state = {
|
|
262
|
+
schemaVersion: SCHEMA_VERSION,
|
|
263
|
+
id,
|
|
264
|
+
status: 'in_progress',
|
|
265
|
+
phase,
|
|
266
|
+
createdAt: new Date().toISOString(),
|
|
267
|
+
updatedAt: new Date().toISOString(),
|
|
268
|
+
scenario: {
|
|
269
|
+
description: options.scenario || '',
|
|
270
|
+
metrics: options.metrics || [],
|
|
271
|
+
successCriteria: options.successCriteria || '',
|
|
272
|
+
scenarios: Array.isArray(options.scenarios) ? options.scenarios : []
|
|
273
|
+
},
|
|
274
|
+
baselines: [],
|
|
275
|
+
hypotheses: [],
|
|
276
|
+
codePaths: [],
|
|
277
|
+
experiments: [],
|
|
278
|
+
results: [],
|
|
279
|
+
breakingPoint: null,
|
|
280
|
+
breakingPointHistory: [],
|
|
281
|
+
constraintResults: [],
|
|
282
|
+
profilingResults: [],
|
|
283
|
+
decision: null
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
assertValid(validateInvestigationState(state), 'Invalid initial investigation state');
|
|
287
|
+
writeInvestigation(state, basePath);
|
|
288
|
+
return state;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Append a line to the investigation log
|
|
293
|
+
* @param {string} id
|
|
294
|
+
* @param {string} content
|
|
295
|
+
* @param {string} basePath
|
|
296
|
+
*/
|
|
297
|
+
function appendInvestigationLog(id, content, basePath = process.cwd()) {
|
|
298
|
+
if (!content) return;
|
|
299
|
+
const logPath = getInvestigationLogPath(id, basePath);
|
|
300
|
+
const entry = content.endsWith('\n') ? content : `${content}\n`;
|
|
301
|
+
fs.appendFileSync(logPath, entry, 'utf8');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Append a baseline section to the investigation log
|
|
306
|
+
* @param {object} input
|
|
307
|
+
* @param {string} input.id
|
|
308
|
+
* @param {string} input.userQuote
|
|
309
|
+
* @param {string} input.command
|
|
310
|
+
* @param {object} input.metrics
|
|
311
|
+
* @param {string} input.baselinePath
|
|
312
|
+
* @param {number} [input.duration]
|
|
313
|
+
* @param {number} [input.runs]
|
|
314
|
+
* @param {string} [input.aggregate]
|
|
315
|
+
* @param {string} [input.date]
|
|
316
|
+
* @param {string} basePath
|
|
317
|
+
*/
|
|
318
|
+
function appendBaselineLog(input, basePath = process.cwd()) {
|
|
319
|
+
if (!input || typeof input !== 'object') {
|
|
320
|
+
throw new Error('appendBaselineLog requires an input object');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const { id, userQuote, command, metrics, baselinePath, date, scenarios, duration, runs, aggregate } = input;
|
|
324
|
+
|
|
325
|
+
if (!id || typeof id !== 'string') {
|
|
326
|
+
throw new Error('appendBaselineLog requires a valid investigation id');
|
|
327
|
+
}
|
|
328
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
329
|
+
throw new Error('appendBaselineLog requires a non-empty userQuote');
|
|
330
|
+
}
|
|
331
|
+
if (!command || typeof command !== 'string') {
|
|
332
|
+
throw new Error('appendBaselineLog requires a non-empty command');
|
|
333
|
+
}
|
|
334
|
+
if (!metrics || typeof metrics !== 'object' || Array.isArray(metrics)) {
|
|
335
|
+
throw new Error('appendBaselineLog requires a metrics object');
|
|
336
|
+
}
|
|
337
|
+
if (!baselinePath || typeof baselinePath !== 'string') {
|
|
338
|
+
throw new Error('appendBaselineLog requires a baselinePath');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
342
|
+
const metricsText = JSON.stringify(metrics);
|
|
343
|
+
const scenarioText = Array.isArray(scenarios) && scenarios.length > 0
|
|
344
|
+
? scenarios.map((scenario) => scenario.name).filter(Boolean).join(', ')
|
|
345
|
+
: '';
|
|
346
|
+
const durationLine = Number.isFinite(duration) ? `- Duration: ${duration}s` : null;
|
|
347
|
+
const runsLine = Number.isFinite(runs) ? `- Runs: ${runs}` : null;
|
|
348
|
+
const aggregateLine = aggregate ? `- Aggregate: ${aggregate}` : null;
|
|
349
|
+
|
|
350
|
+
const entry = [
|
|
351
|
+
`## Baseline - ${logDate}`,
|
|
352
|
+
'',
|
|
353
|
+
`**User Quote:** "${userQuote}"`,
|
|
354
|
+
'',
|
|
355
|
+
'**Summary**',
|
|
356
|
+
scenarioText ? `- Scenarios: ${scenarioText}` : null,
|
|
357
|
+
`- Baseline command: \`${command}\``,
|
|
358
|
+
durationLine,
|
|
359
|
+
runsLine,
|
|
360
|
+
aggregateLine,
|
|
361
|
+
`- Metrics: ${metricsText}`,
|
|
362
|
+
'',
|
|
363
|
+
'**Evidence**',
|
|
364
|
+
`- Baseline file: ${baselinePath}`,
|
|
365
|
+
''
|
|
366
|
+
].filter(Boolean).join('\n');
|
|
367
|
+
|
|
368
|
+
appendInvestigationLog(id, entry, basePath);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Append a profiling section to the investigation log
|
|
373
|
+
* @param {object} input
|
|
374
|
+
* @param {string} input.id
|
|
375
|
+
* @param {string} input.userQuote
|
|
376
|
+
* @param {string} input.tool
|
|
377
|
+
* @param {string} input.command
|
|
378
|
+
* @param {string[]} input.artifacts
|
|
379
|
+
* @param {string[]} input.hotspots
|
|
380
|
+
* @param {string} [input.date]
|
|
381
|
+
* @param {string} basePath
|
|
382
|
+
*/
|
|
383
|
+
function appendProfilingLog(input, basePath = process.cwd()) {
|
|
384
|
+
if (!input || typeof input !== 'object') {
|
|
385
|
+
throw new Error('appendProfilingLog requires an input object');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const { id, userQuote, tool, command, artifacts, hotspots, date } = input;
|
|
389
|
+
|
|
390
|
+
if (!id || typeof id !== 'string') {
|
|
391
|
+
throw new Error('appendProfilingLog requires a valid investigation id');
|
|
392
|
+
}
|
|
393
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
394
|
+
throw new Error('appendProfilingLog requires a non-empty userQuote');
|
|
395
|
+
}
|
|
396
|
+
if (!tool || typeof tool !== 'string') {
|
|
397
|
+
throw new Error('appendProfilingLog requires a tool');
|
|
398
|
+
}
|
|
399
|
+
if (!command || typeof command !== 'string') {
|
|
400
|
+
throw new Error('appendProfilingLog requires a command');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
404
|
+
const artifactList = Array.isArray(artifacts) ? artifacts : [];
|
|
405
|
+
const hotspotList = Array.isArray(hotspots) ? hotspots : [];
|
|
406
|
+
|
|
407
|
+
const entry = [
|
|
408
|
+
`## Profiling - ${logDate}`,
|
|
409
|
+
'',
|
|
410
|
+
`**User Quote:** "${userQuote}"`,
|
|
411
|
+
'',
|
|
412
|
+
'**Summary**',
|
|
413
|
+
`- Tool: ${tool}`,
|
|
414
|
+
`- Command: \`${command}\``,
|
|
415
|
+
'',
|
|
416
|
+
'**Evidence**',
|
|
417
|
+
artifactList.length ? `- Artifacts: ${artifactList.join(', ')}` : '- Artifacts: n/a',
|
|
418
|
+
hotspotList.length ? `- Hotspots: ${hotspotList.join(', ')}` : '- Hotspots: n/a',
|
|
419
|
+
''
|
|
420
|
+
].join('\n');
|
|
421
|
+
|
|
422
|
+
appendInvestigationLog(id, entry, basePath);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Append a decision section to the investigation log
|
|
427
|
+
* @param {object} input
|
|
428
|
+
* @param {string} input.id
|
|
429
|
+
* @param {string} input.userQuote
|
|
430
|
+
* @param {string} input.verdict
|
|
431
|
+
* @param {string} input.rationale
|
|
432
|
+
* @param {string} [input.date]
|
|
433
|
+
* @param {string} basePath
|
|
434
|
+
*/
|
|
435
|
+
function appendDecisionLog(input, basePath = process.cwd()) {
|
|
436
|
+
if (!input || typeof input !== 'object') {
|
|
437
|
+
throw new Error('appendDecisionLog requires an input object');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const { id, userQuote, verdict, rationale, date, resultsCount } = input;
|
|
441
|
+
|
|
442
|
+
if (!id || typeof id !== 'string') {
|
|
443
|
+
throw new Error('appendDecisionLog requires a valid investigation id');
|
|
444
|
+
}
|
|
445
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
446
|
+
throw new Error('appendDecisionLog requires a non-empty userQuote');
|
|
447
|
+
}
|
|
448
|
+
if (!verdict || typeof verdict !== 'string') {
|
|
449
|
+
throw new Error('appendDecisionLog requires a verdict');
|
|
450
|
+
}
|
|
451
|
+
if (!rationale || typeof rationale !== 'string') {
|
|
452
|
+
throw new Error('appendDecisionLog requires a rationale');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
456
|
+
const resultCountText = typeof resultsCount === 'number' ? String(resultsCount) : 'n/a';
|
|
457
|
+
|
|
458
|
+
const entry = [
|
|
459
|
+
`## Decision - ${logDate}`,
|
|
460
|
+
'',
|
|
461
|
+
`**User Quote:** "${userQuote}"`,
|
|
462
|
+
'',
|
|
463
|
+
'**Summary**',
|
|
464
|
+
`- Verdict: ${verdict}`,
|
|
465
|
+
`- Rationale: ${rationale}`,
|
|
466
|
+
'',
|
|
467
|
+
'**Evidence**',
|
|
468
|
+
`- Results count: ${resultCountText}`,
|
|
469
|
+
''
|
|
470
|
+
].join('\n');
|
|
471
|
+
|
|
472
|
+
appendInvestigationLog(id, entry, basePath);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Append a setup section to the investigation log
|
|
477
|
+
* @param {object} input
|
|
478
|
+
* @param {string} input.id
|
|
479
|
+
* @param {string} input.userQuote
|
|
480
|
+
* @param {string} input.scenario
|
|
481
|
+
* @param {string} input.command
|
|
482
|
+
* @param {string} input.version
|
|
483
|
+
* @param {number} [input.duration]
|
|
484
|
+
* @param {number} [input.runs]
|
|
485
|
+
* @param {string} [input.aggregate]
|
|
486
|
+
* @param {string} [input.date]
|
|
487
|
+
* @param {string} basePath
|
|
488
|
+
*/
|
|
489
|
+
function appendSetupLog(input, basePath = process.cwd()) {
|
|
490
|
+
if (!input || typeof input !== 'object') {
|
|
491
|
+
throw new Error('appendSetupLog requires an input object');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const { id, userQuote, scenario, command, version, duration, runs, aggregate, date } = input;
|
|
495
|
+
|
|
496
|
+
if (!id || typeof id !== 'string') {
|
|
497
|
+
throw new Error('appendSetupLog requires a valid investigation id');
|
|
498
|
+
}
|
|
499
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
500
|
+
throw new Error('appendSetupLog requires a non-empty userQuote');
|
|
501
|
+
}
|
|
502
|
+
if (!scenario || typeof scenario !== 'string') {
|
|
503
|
+
throw new Error('appendSetupLog requires a scenario');
|
|
504
|
+
}
|
|
505
|
+
if (!command || typeof command !== 'string') {
|
|
506
|
+
throw new Error('appendSetupLog requires a command');
|
|
507
|
+
}
|
|
508
|
+
if (!version || typeof version !== 'string') {
|
|
509
|
+
throw new Error('appendSetupLog requires a version');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
513
|
+
const durationLine = Number.isFinite(duration) ? `- Duration: ${duration}s` : null;
|
|
514
|
+
const runsLine = Number.isFinite(runs) ? `- Runs: ${runs}` : null;
|
|
515
|
+
const aggregateLine = aggregate ? `- Aggregate: ${aggregate}` : null;
|
|
516
|
+
const entry = [
|
|
517
|
+
`## Setup - ${logDate}`,
|
|
518
|
+
'',
|
|
519
|
+
`**User Quote:** "${userQuote}"`,
|
|
520
|
+
'',
|
|
521
|
+
'**Summary**',
|
|
522
|
+
`- Scenario: ${scenario}`,
|
|
523
|
+
`- Command: \`${command}\``,
|
|
524
|
+
`- Version: ${version}`,
|
|
525
|
+
durationLine,
|
|
526
|
+
runsLine,
|
|
527
|
+
aggregateLine,
|
|
528
|
+
'',
|
|
529
|
+
'**Evidence**',
|
|
530
|
+
`- Command: \`${command}\``,
|
|
531
|
+
`- Version: ${version}`,
|
|
532
|
+
''
|
|
533
|
+
].filter(Boolean).join('\n');
|
|
534
|
+
|
|
535
|
+
appendInvestigationLog(id, entry, basePath);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Append a breaking point section to the investigation log
|
|
540
|
+
* @param {object} input
|
|
541
|
+
* @param {string} input.id
|
|
542
|
+
* @param {string} input.userQuote
|
|
543
|
+
* @param {string} input.paramEnv
|
|
544
|
+
* @param {number} input.min
|
|
545
|
+
* @param {number} input.max
|
|
546
|
+
* @param {number|null} input.breakingPoint
|
|
547
|
+
* @param {string} [input.date]
|
|
548
|
+
* @param {string} basePath
|
|
549
|
+
*/
|
|
550
|
+
function appendBreakingPointLog(input, basePath = process.cwd()) {
|
|
551
|
+
if (!input || typeof input !== 'object') {
|
|
552
|
+
throw new Error('appendBreakingPointLog requires an input object');
|
|
553
|
+
}
|
|
554
|
+
const { id, userQuote, paramEnv, min, max, breakingPoint, history, date } = input;
|
|
555
|
+
|
|
556
|
+
if (!id || typeof id !== 'string') {
|
|
557
|
+
throw new Error('appendBreakingPointLog requires a valid investigation id');
|
|
558
|
+
}
|
|
559
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
560
|
+
throw new Error('appendBreakingPointLog requires a non-empty userQuote');
|
|
561
|
+
}
|
|
562
|
+
if (!paramEnv || typeof paramEnv !== 'string') {
|
|
563
|
+
throw new Error('appendBreakingPointLog requires a paramEnv');
|
|
564
|
+
}
|
|
565
|
+
if (typeof min !== 'number' || typeof max !== 'number') {
|
|
566
|
+
throw new Error('appendBreakingPointLog requires numeric min/max');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
570
|
+
const historyText = Array.isArray(history) ? JSON.stringify(history) : 'n/a';
|
|
571
|
+
const entry = [
|
|
572
|
+
`## Breaking Point - ${logDate}`,
|
|
573
|
+
'',
|
|
574
|
+
`**User Quote:** "${userQuote}"`,
|
|
575
|
+
'',
|
|
576
|
+
'**Summary**',
|
|
577
|
+
`- Param env: ${paramEnv}`,
|
|
578
|
+
`- Range: ${min}..${max}`,
|
|
579
|
+
`- Breaking point: ${breakingPoint ?? 'n/a'}`,
|
|
580
|
+
'',
|
|
581
|
+
'**Evidence**',
|
|
582
|
+
`- History: ${historyText}`,
|
|
583
|
+
''
|
|
584
|
+
].join('\n');
|
|
585
|
+
|
|
586
|
+
appendInvestigationLog(id, entry, basePath);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Append a constraints section to the investigation log
|
|
591
|
+
* @param {object} input
|
|
592
|
+
* @param {string} input.id
|
|
593
|
+
* @param {string} input.userQuote
|
|
594
|
+
* @param {object} input.constraints
|
|
595
|
+
* @param {object} input.delta
|
|
596
|
+
* @param {string} [input.date]
|
|
597
|
+
* @param {string} basePath
|
|
598
|
+
*/
|
|
599
|
+
function appendConstraintLog(input, basePath = process.cwd()) {
|
|
600
|
+
if (!input || typeof input !== 'object') {
|
|
601
|
+
throw new Error('appendConstraintLog requires an input object');
|
|
602
|
+
}
|
|
603
|
+
const { id, userQuote, constraints, delta, date } = input;
|
|
604
|
+
|
|
605
|
+
if (!id || typeof id !== 'string') {
|
|
606
|
+
throw new Error('appendConstraintLog requires a valid investigation id');
|
|
607
|
+
}
|
|
608
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
609
|
+
throw new Error('appendConstraintLog requires a non-empty userQuote');
|
|
610
|
+
}
|
|
611
|
+
if (!constraints || typeof constraints !== 'object') {
|
|
612
|
+
throw new Error('appendConstraintLog requires constraints');
|
|
613
|
+
}
|
|
614
|
+
if (!delta || typeof delta !== 'object') {
|
|
615
|
+
throw new Error('appendConstraintLog requires delta');
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
619
|
+
const entry = [
|
|
620
|
+
`## Constraints - ${logDate}`,
|
|
621
|
+
'',
|
|
622
|
+
`**User Quote:** "${userQuote}"`,
|
|
623
|
+
'',
|
|
624
|
+
'**Summary**',
|
|
625
|
+
`- CPU: ${constraints.cpu || 'n/a'}`,
|
|
626
|
+
`- Memory: ${constraints.memory || 'n/a'}`,
|
|
627
|
+
'',
|
|
628
|
+
'**Evidence**',
|
|
629
|
+
`- Delta: ${JSON.stringify(delta.metrics || {})}`,
|
|
630
|
+
''
|
|
631
|
+
].join('\n');
|
|
632
|
+
|
|
633
|
+
appendInvestigationLog(id, entry, basePath);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Append a hypotheses section to the investigation log
|
|
638
|
+
* @param {object} input
|
|
639
|
+
* @param {string} input.id
|
|
640
|
+
* @param {string} input.userQuote
|
|
641
|
+
* @param {Array} input.hypotheses
|
|
642
|
+
* @param {string} [input.date]
|
|
643
|
+
* @param {string} basePath
|
|
644
|
+
*/
|
|
645
|
+
function appendHypothesesLog(input, basePath = process.cwd()) {
|
|
646
|
+
if (!input || typeof input !== 'object') {
|
|
647
|
+
throw new Error('appendHypothesesLog requires an input object');
|
|
648
|
+
}
|
|
649
|
+
const { id, userQuote, hypotheses, date, gitHistory, hypothesesFile } = input;
|
|
650
|
+
|
|
651
|
+
if (!id || typeof id !== 'string') {
|
|
652
|
+
throw new Error('appendHypothesesLog requires a valid investigation id');
|
|
653
|
+
}
|
|
654
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
655
|
+
throw new Error('appendHypothesesLog requires a non-empty userQuote');
|
|
656
|
+
}
|
|
657
|
+
if (!Array.isArray(hypotheses)) {
|
|
658
|
+
throw new Error('appendHypothesesLog requires hypotheses array');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
662
|
+
const lines = hypotheses.map((item) => {
|
|
663
|
+
if (!item) return null;
|
|
664
|
+
const label = item.id ? `${item.id}: ` : '';
|
|
665
|
+
const evidence = item.evidence ? ` (evidence: ${item.evidence})` : '';
|
|
666
|
+
const confidence = item.confidence ? ` [${item.confidence}]` : '';
|
|
667
|
+
return `- ${label}${item.hypothesis || 'n/a'}${confidence}${evidence}`;
|
|
668
|
+
}).filter(Boolean);
|
|
669
|
+
|
|
670
|
+
const entry = [
|
|
671
|
+
`## Hypotheses - ${logDate}`,
|
|
672
|
+
'',
|
|
673
|
+
`**User Quote:** "${userQuote}"`,
|
|
674
|
+
'',
|
|
675
|
+
'**Summary**',
|
|
676
|
+
lines.length > 0 ? lines.join('\n') : '- n/a',
|
|
677
|
+
'',
|
|
678
|
+
'**Evidence**',
|
|
679
|
+
`- Git history: ${Array.isArray(gitHistory) && gitHistory.length ? gitHistory.join(' | ') : 'n/a'}`,
|
|
680
|
+
hypothesesFile ? `- Hypotheses file: ${hypothesesFile}` : null,
|
|
681
|
+
''
|
|
682
|
+
].join('\n');
|
|
683
|
+
|
|
684
|
+
appendInvestigationLog(id, entry, basePath);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Append a code-paths section to the investigation log
|
|
689
|
+
* @param {object} input
|
|
690
|
+
* @param {string} input.id
|
|
691
|
+
* @param {string} input.userQuote
|
|
692
|
+
* @param {string[]} input.keywords
|
|
693
|
+
* @param {Array} input.paths
|
|
694
|
+
* @param {string} [input.date]
|
|
695
|
+
* @param {string} basePath
|
|
696
|
+
*/
|
|
697
|
+
function appendCodePathsLog(input, basePath = process.cwd()) {
|
|
698
|
+
if (!input || typeof input !== 'object') {
|
|
699
|
+
throw new Error('appendCodePathsLog requires an input object');
|
|
700
|
+
}
|
|
701
|
+
const { id, userQuote, keywords, paths, date, repoMapStatus } = input;
|
|
702
|
+
|
|
703
|
+
if (!id || typeof id !== 'string') {
|
|
704
|
+
throw new Error('appendCodePathsLog requires a valid investigation id');
|
|
705
|
+
}
|
|
706
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
707
|
+
throw new Error('appendCodePathsLog requires a non-empty userQuote');
|
|
708
|
+
}
|
|
709
|
+
if (!Array.isArray(paths)) {
|
|
710
|
+
throw new Error('appendCodePathsLog requires paths array');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
714
|
+
const keywordText = Array.isArray(keywords) && keywords.length > 0 ? keywords.join(', ') : 'n/a';
|
|
715
|
+
const pathLines = paths.map((pathEntry) => {
|
|
716
|
+
const file = pathEntry.file || 'n/a';
|
|
717
|
+
const score = typeof pathEntry.score === 'number' ? ` (score: ${pathEntry.score})` : '';
|
|
718
|
+
const symbols = Array.isArray(pathEntry.symbols) && pathEntry.symbols.length > 0
|
|
719
|
+
? ` [${pathEntry.symbols.join(', ')}]`
|
|
720
|
+
: '';
|
|
721
|
+
return `- ${file}${score}${symbols}`;
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
const entry = [
|
|
725
|
+
`## Code Paths - ${logDate}`,
|
|
726
|
+
'',
|
|
727
|
+
`**User Quote:** "${userQuote}"`,
|
|
728
|
+
'',
|
|
729
|
+
'**Summary**',
|
|
730
|
+
`- Keywords: ${keywordText}`,
|
|
731
|
+
pathLines.length > 0 ? pathLines.join('\n') : '- n/a',
|
|
732
|
+
'',
|
|
733
|
+
'**Evidence**',
|
|
734
|
+
`- Repo map: ${repoMapStatus || 'n/a'}`,
|
|
735
|
+
`- Paths count: ${pathLines.length}`,
|
|
736
|
+
''
|
|
737
|
+
].join('\n');
|
|
738
|
+
|
|
739
|
+
appendInvestigationLog(id, entry, basePath);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Append an optimization section to the investigation log
|
|
744
|
+
* @param {object} input
|
|
745
|
+
* @param {string} input.id
|
|
746
|
+
* @param {string} input.userQuote
|
|
747
|
+
* @param {string} input.change
|
|
748
|
+
* @param {object} input.delta
|
|
749
|
+
* @param {string} input.verdict
|
|
750
|
+
* @param {number} [input.runs]
|
|
751
|
+
* @param {string} [input.aggregate]
|
|
752
|
+
* @param {string} [input.date]
|
|
753
|
+
* @param {string} basePath
|
|
754
|
+
*/
|
|
755
|
+
function appendOptimizationLog(input, basePath = process.cwd()) {
|
|
756
|
+
if (!input || typeof input !== 'object') {
|
|
757
|
+
throw new Error('appendOptimizationLog requires an input object');
|
|
758
|
+
}
|
|
759
|
+
const { id, userQuote, change, delta, verdict, date, gitHistory, runs, aggregate } = input;
|
|
760
|
+
|
|
761
|
+
if (!id || typeof id !== 'string') {
|
|
762
|
+
throw new Error('appendOptimizationLog requires a valid investigation id');
|
|
763
|
+
}
|
|
764
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
765
|
+
throw new Error('appendOptimizationLog requires a non-empty userQuote');
|
|
766
|
+
}
|
|
767
|
+
if (!change || typeof change !== 'string') {
|
|
768
|
+
throw new Error('appendOptimizationLog requires a change summary');
|
|
769
|
+
}
|
|
770
|
+
if (!delta || typeof delta !== 'object') {
|
|
771
|
+
throw new Error('appendOptimizationLog requires delta');
|
|
772
|
+
}
|
|
773
|
+
if (!verdict || typeof verdict !== 'string') {
|
|
774
|
+
throw new Error('appendOptimizationLog requires a verdict');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
778
|
+
const gitHistoryText = Array.isArray(gitHistory) && gitHistory.length
|
|
779
|
+
? gitHistory.join(' | ')
|
|
780
|
+
: 'n/a';
|
|
781
|
+
const runsLine = Number.isFinite(runs) ? `- Runs: ${runs}` : null;
|
|
782
|
+
const aggregateLine = aggregate ? `- Aggregate: ${aggregate}` : null;
|
|
783
|
+
const entry = [
|
|
784
|
+
`## Optimization - ${logDate}`,
|
|
785
|
+
'',
|
|
786
|
+
`**User Quote:** "${userQuote}"`,
|
|
787
|
+
'',
|
|
788
|
+
'**Summary**',
|
|
789
|
+
`- Change: ${change}`,
|
|
790
|
+
`- Verdict: ${verdict}`,
|
|
791
|
+
runsLine,
|
|
792
|
+
aggregateLine,
|
|
793
|
+
'',
|
|
794
|
+
'**Evidence**',
|
|
795
|
+
`- Delta: ${JSON.stringify(delta.metrics || {})}`,
|
|
796
|
+
`- Git history: ${gitHistoryText}`,
|
|
797
|
+
''
|
|
798
|
+
].join('\n');
|
|
799
|
+
|
|
800
|
+
appendInvestigationLog(id, entry, basePath);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Append a consolidation section to the investigation log
|
|
805
|
+
* @param {object} input
|
|
806
|
+
* @param {string} input.id
|
|
807
|
+
* @param {string} input.userQuote
|
|
808
|
+
* @param {string} input.version
|
|
809
|
+
* @param {string} input.path
|
|
810
|
+
* @param {string} [input.date]
|
|
811
|
+
* @param {string} basePath
|
|
812
|
+
*/
|
|
813
|
+
function appendConsolidationLog(input, basePath = process.cwd()) {
|
|
814
|
+
if (!input || typeof input !== 'object') {
|
|
815
|
+
throw new Error('appendConsolidationLog requires an input object');
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const { id, userQuote, version, path, date } = input;
|
|
819
|
+
|
|
820
|
+
if (!id || typeof id !== 'string') {
|
|
821
|
+
throw new Error('appendConsolidationLog requires a valid investigation id');
|
|
822
|
+
}
|
|
823
|
+
if (!userQuote || typeof userQuote !== 'string') {
|
|
824
|
+
throw new Error('appendConsolidationLog requires a non-empty userQuote');
|
|
825
|
+
}
|
|
826
|
+
if (!version || typeof version !== 'string') {
|
|
827
|
+
throw new Error('appendConsolidationLog requires a version');
|
|
828
|
+
}
|
|
829
|
+
if (!path || typeof path !== 'string') {
|
|
830
|
+
throw new Error('appendConsolidationLog requires a path');
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const logDate = date || new Date().toISOString().slice(0, 10);
|
|
834
|
+
const entry = [
|
|
835
|
+
`## Consolidation - ${logDate}`,
|
|
836
|
+
'',
|
|
837
|
+
`**User Quote:** "${userQuote}"`,
|
|
838
|
+
'',
|
|
839
|
+
'**Summary**',
|
|
840
|
+
`- Version: ${version}`,
|
|
841
|
+
`- Baseline file: ${path}`,
|
|
842
|
+
'',
|
|
843
|
+
'**Evidence**',
|
|
844
|
+
`- Baseline file: ${path}`,
|
|
845
|
+
''
|
|
846
|
+
].join('\n');
|
|
847
|
+
|
|
848
|
+
appendInvestigationLog(id, entry, basePath);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
module.exports = {
|
|
852
|
+
SCHEMA_VERSION,
|
|
853
|
+
PHASES,
|
|
854
|
+
generateInvestigationId,
|
|
855
|
+
getPerfDir,
|
|
856
|
+
ensurePerfDirs,
|
|
857
|
+
getInvestigationPath,
|
|
858
|
+
getInvestigationLogPath,
|
|
859
|
+
readInvestigation,
|
|
860
|
+
writeInvestigation,
|
|
861
|
+
updateInvestigation,
|
|
862
|
+
initializeInvestigation,
|
|
863
|
+
appendInvestigationLog,
|
|
864
|
+
appendBaselineLog,
|
|
865
|
+
appendProfilingLog,
|
|
866
|
+
appendDecisionLog,
|
|
867
|
+
appendSetupLog,
|
|
868
|
+
appendBreakingPointLog,
|
|
869
|
+
appendConstraintLog,
|
|
870
|
+
appendHypothesesLog,
|
|
871
|
+
appendCodePathsLog,
|
|
872
|
+
appendOptimizationLog,
|
|
873
|
+
appendConsolidationLog
|
|
874
|
+
};
|