dw-kit 1.9.0 → 1.9.2
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/agents/planner.md +100 -100
- package/.claude/agents/quality-checker.md +86 -86
- package/.claude/agents/researcher.md +93 -93
- package/.claude/agents/reviewer.md +126 -126
- package/.claude/hooks/supply-chain-scan.sh +0 -0
- package/.claude/rules/code-style.md +37 -37
- package/.claude/settings.json +2 -28
- package/.claude/skills/dw-kit-report/SKILL.md +38 -7
- package/.claude/skills/dw-plan/template-plan.md +47 -47
- package/.claude/skills/dw-research/template-research.md +51 -51
- package/.claude/skills/dw-review/checklist.md +88 -88
- package/.claude/skills/dw-thinking/THINKING.md +91 -91
- package/.claude/templates/agent-report.md +35 -35
- package/.claude/templates/en/task-context.md +77 -73
- package/.claude/templates/en/task-plan.md +83 -79
- package/.claude/templates/en/task-progress.md +69 -65
- package/.claude/templates/pr-template.md +56 -56
- package/.claude/templates/task-context.md +77 -73
- package/.claude/templates/task-plan.md +83 -79
- package/.claude/templates/task-progress.md +69 -65
- package/.dw/adapters/claude-cli/extensions/README.md +36 -36
- package/.dw/adapters/claude-cli/generated/README.md +23 -23
- package/.dw/adapters/claude-cli/overrides/README.md +37 -37
- package/.dw/adapters/generic/README.md +21 -21
- package/.dw/config/presets/enterprise.yml +52 -52
- package/.dw/config/presets/small-team.yml +39 -39
- package/.dw/config/presets/solo-quick.yml +37 -37
- package/.dw/core/AGENTS.md +53 -53
- package/.dw/core/QUALITY.md +220 -220
- package/.dw/core/THINKING.md +126 -126
- package/.dw/core/WORKFLOW.md +17 -12
- package/.dw/core/templates/v2/spec.md +2 -0
- package/.dw/core/templates/v2/tracking.md +2 -0
- package/.dw/core/templates/v3/task.md +15 -22
- package/.dw/core/templates/vi/task-context.md +96 -92
- package/.dw/core/templates/vi/task-plan.md +97 -93
- package/.dw/core/templates/vi/task-progress.md +60 -56
- package/LICENSE +201 -201
- package/NOTICE +26 -26
- package/README.md +1 -1
- package/bin/dw.mjs +28 -28
- package/package.json +1 -1
- package/src/commands/claude-vn-fix.mjs +267 -267
- package/src/commands/prompt.mjs +112 -112
- package/src/commands/validate.mjs +102 -102
- package/src/lib/clipboard.mjs +24 -24
- package/src/lib/goal-store.mjs +2 -14
- package/src/lib/platform.mjs +39 -39
- package/src/lib/prompt-suggest.mjs +84 -84
- package/src/lib/timeline-parser.mjs +54 -15
- package/src/lib/ui.mjs +66 -66
- package/src/lib/update-checker.mjs +73 -73
- package/.dw/security/advisory-snapshot.json +0 -157
package/src/commands/prompt.mjs
CHANGED
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
import { header, info, ok, warn, err, log } from '../lib/ui.mjs';
|
|
2
|
-
import { copyToClipboard } from '../lib/clipboard.mjs';
|
|
3
|
-
import { getSuggestions, isVague, expandTemplate } from '../lib/prompt-suggest.mjs';
|
|
4
|
-
import { detectPlatform } from '../lib/platform.mjs';
|
|
5
|
-
|
|
6
|
-
export async function promptCommand(opts) {
|
|
7
|
-
header('dw-kit Prompt Builder');
|
|
8
|
-
|
|
9
|
-
const adapter = detectPlatform(process.cwd());
|
|
10
|
-
|
|
11
|
-
// Non-interactive mode: --text <text>
|
|
12
|
-
if (opts.text !== undefined) {
|
|
13
|
-
if (!opts.text.trim()) {
|
|
14
|
-
err('--text cannot be empty.');
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
const result = await buildPrompt(opts.text, { interactive: false });
|
|
18
|
-
outputResult(result, adapter);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Interactive mode
|
|
23
|
-
const result = await buildPrompt('', { interactive: true });
|
|
24
|
-
outputResult(result, adapter);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function buildPrompt(initialText, { interactive }) {
|
|
28
|
-
let description = initialText;
|
|
29
|
-
|
|
30
|
-
if (interactive) {
|
|
31
|
-
description = await runAutocompleteStep();
|
|
32
|
-
if (!description.trim()) {
|
|
33
|
-
err('No description provided.');
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let area = '';
|
|
39
|
-
let outcome = '';
|
|
40
|
-
|
|
41
|
-
if (isVague(description)) {
|
|
42
|
-
if (interactive) {
|
|
43
|
-
info('Description seems short — a couple of quick questions (Enter to skip):');
|
|
44
|
-
({ area, outcome } = await runWizardStep());
|
|
45
|
-
}
|
|
46
|
-
// In non-interactive mode: expand with just the description (no wizard data)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return expandTemplate(description, { area, outcome });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function runAutocompleteStep() {
|
|
53
|
-
const { AutoComplete } = await import('enquirer');
|
|
54
|
-
const suggestions = getSuggestions(process.cwd());
|
|
55
|
-
|
|
56
|
-
const prompt = new AutoComplete({
|
|
57
|
-
name: 'description',
|
|
58
|
-
message: 'Describe your task:',
|
|
59
|
-
limit: 7,
|
|
60
|
-
choices: suggestions.length ? suggestions : ['(no suggestions — type your task)'],
|
|
61
|
-
suggest(typed, choices) {
|
|
62
|
-
const lower = typed.toLowerCase();
|
|
63
|
-
const filtered = choices.filter((c) => c.message.toLowerCase().includes(lower));
|
|
64
|
-
// Always offer the raw typed text as first selectable option
|
|
65
|
-
if (typed && !filtered.find((c) => c.message === typed)) {
|
|
66
|
-
return [{ message: typed, value: typed }, ...filtered];
|
|
67
|
-
}
|
|
68
|
-
return filtered.length ? filtered : [{ message: typed || '(empty)', value: typed }];
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return prompt.run();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function runWizardStep() {
|
|
76
|
-
const { Input } = await import('enquirer');
|
|
77
|
-
|
|
78
|
-
const area = await new Input({
|
|
79
|
-
name: 'area',
|
|
80
|
-
message: 'Which area/files? (e.g. auth middleware, src/login/)',
|
|
81
|
-
initial: '',
|
|
82
|
-
}).run().catch(() => '');
|
|
83
|
-
|
|
84
|
-
const outcome = await new Input({
|
|
85
|
-
name: 'outcome',
|
|
86
|
-
message: 'Expected outcome? (e.g. user redirected to /dashboard)',
|
|
87
|
-
initial: '',
|
|
88
|
-
}).run().catch(() => '');
|
|
89
|
-
|
|
90
|
-
return { area, outcome };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function outputResult(text, adapter) {
|
|
94
|
-
log('');
|
|
95
|
-
log('─── Result ───────────────────────────────────────────');
|
|
96
|
-
log(text);
|
|
97
|
-
log('──────────────────────────────────────────────────────');
|
|
98
|
-
log('');
|
|
99
|
-
|
|
100
|
-
const shouldCopy = adapter === 'claude-cli' || adapter === 'cursor';
|
|
101
|
-
if (shouldCopy) {
|
|
102
|
-
const copied = copyToClipboard(text);
|
|
103
|
-
if (copied) {
|
|
104
|
-
ok('Copied to clipboard. Paste into Claude CLI or your IDE.');
|
|
105
|
-
} else {
|
|
106
|
-
warn('Clipboard copy failed. Copy the text above manually.');
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
// generic adapter: just output to stdout
|
|
110
|
-
info('(generic adapter — copy the text above manually)');
|
|
111
|
-
}
|
|
112
|
-
}
|
|
1
|
+
import { header, info, ok, warn, err, log } from '../lib/ui.mjs';
|
|
2
|
+
import { copyToClipboard } from '../lib/clipboard.mjs';
|
|
3
|
+
import { getSuggestions, isVague, expandTemplate } from '../lib/prompt-suggest.mjs';
|
|
4
|
+
import { detectPlatform } from '../lib/platform.mjs';
|
|
5
|
+
|
|
6
|
+
export async function promptCommand(opts) {
|
|
7
|
+
header('dw-kit Prompt Builder');
|
|
8
|
+
|
|
9
|
+
const adapter = detectPlatform(process.cwd());
|
|
10
|
+
|
|
11
|
+
// Non-interactive mode: --text <text>
|
|
12
|
+
if (opts.text !== undefined) {
|
|
13
|
+
if (!opts.text.trim()) {
|
|
14
|
+
err('--text cannot be empty.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const result = await buildPrompt(opts.text, { interactive: false });
|
|
18
|
+
outputResult(result, adapter);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Interactive mode
|
|
23
|
+
const result = await buildPrompt('', { interactive: true });
|
|
24
|
+
outputResult(result, adapter);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function buildPrompt(initialText, { interactive }) {
|
|
28
|
+
let description = initialText;
|
|
29
|
+
|
|
30
|
+
if (interactive) {
|
|
31
|
+
description = await runAutocompleteStep();
|
|
32
|
+
if (!description.trim()) {
|
|
33
|
+
err('No description provided.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let area = '';
|
|
39
|
+
let outcome = '';
|
|
40
|
+
|
|
41
|
+
if (isVague(description)) {
|
|
42
|
+
if (interactive) {
|
|
43
|
+
info('Description seems short — a couple of quick questions (Enter to skip):');
|
|
44
|
+
({ area, outcome } = await runWizardStep());
|
|
45
|
+
}
|
|
46
|
+
// In non-interactive mode: expand with just the description (no wizard data)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return expandTemplate(description, { area, outcome });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function runAutocompleteStep() {
|
|
53
|
+
const { AutoComplete } = await import('enquirer');
|
|
54
|
+
const suggestions = getSuggestions(process.cwd());
|
|
55
|
+
|
|
56
|
+
const prompt = new AutoComplete({
|
|
57
|
+
name: 'description',
|
|
58
|
+
message: 'Describe your task:',
|
|
59
|
+
limit: 7,
|
|
60
|
+
choices: suggestions.length ? suggestions : ['(no suggestions — type your task)'],
|
|
61
|
+
suggest(typed, choices) {
|
|
62
|
+
const lower = typed.toLowerCase();
|
|
63
|
+
const filtered = choices.filter((c) => c.message.toLowerCase().includes(lower));
|
|
64
|
+
// Always offer the raw typed text as first selectable option
|
|
65
|
+
if (typed && !filtered.find((c) => c.message === typed)) {
|
|
66
|
+
return [{ message: typed, value: typed }, ...filtered];
|
|
67
|
+
}
|
|
68
|
+
return filtered.length ? filtered : [{ message: typed || '(empty)', value: typed }];
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return prompt.run();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function runWizardStep() {
|
|
76
|
+
const { Input } = await import('enquirer');
|
|
77
|
+
|
|
78
|
+
const area = await new Input({
|
|
79
|
+
name: 'area',
|
|
80
|
+
message: 'Which area/files? (e.g. auth middleware, src/login/)',
|
|
81
|
+
initial: '',
|
|
82
|
+
}).run().catch(() => '');
|
|
83
|
+
|
|
84
|
+
const outcome = await new Input({
|
|
85
|
+
name: 'outcome',
|
|
86
|
+
message: 'Expected outcome? (e.g. user redirected to /dashboard)',
|
|
87
|
+
initial: '',
|
|
88
|
+
}).run().catch(() => '');
|
|
89
|
+
|
|
90
|
+
return { area, outcome };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function outputResult(text, adapter) {
|
|
94
|
+
log('');
|
|
95
|
+
log('─── Result ───────────────────────────────────────────');
|
|
96
|
+
log(text);
|
|
97
|
+
log('──────────────────────────────────────────────────────');
|
|
98
|
+
log('');
|
|
99
|
+
|
|
100
|
+
const shouldCopy = adapter === 'claude-cli' || adapter === 'cursor';
|
|
101
|
+
if (shouldCopy) {
|
|
102
|
+
const copied = copyToClipboard(text);
|
|
103
|
+
if (copied) {
|
|
104
|
+
ok('Copied to clipboard. Paste into Claude CLI or your IDE.');
|
|
105
|
+
} else {
|
|
106
|
+
warn('Clipboard copy failed. Copy the text above manually.');
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// generic adapter: just output to stdout
|
|
110
|
+
info('(generic adapter — copy the text above manually)');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import Ajv from 'ajv';
|
|
5
|
-
import { header, ok, warn, err, info, log } from '../lib/ui.mjs';
|
|
6
|
-
import { loadConfig, loadSchema } from '../lib/config.mjs';
|
|
7
|
-
|
|
8
|
-
const TOOLKIT_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
9
|
-
|
|
10
|
-
export async function validateCommand(opts) {
|
|
11
|
-
const configPath = resolve(opts.file);
|
|
12
|
-
|
|
13
|
-
header('dw-kit Config Validation');
|
|
14
|
-
|
|
15
|
-
if (!existsSync(configPath)) {
|
|
16
|
-
err(`Config file not found: ${configPath}`);
|
|
17
|
-
log('Run `dw init` to create a config file.');
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
info(`Loading ${opts.file}`);
|
|
22
|
-
const config = loadConfig(configPath);
|
|
23
|
-
if (!config) {
|
|
24
|
-
err('Failed to parse YAML config.');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
ok('YAML syntax valid');
|
|
28
|
-
|
|
29
|
-
info('Validating against schema');
|
|
30
|
-
const schemaPath = resolve('.dw', 'config', 'config.schema.json');
|
|
31
|
-
const fallbackSchemaPath = join(TOOLKIT_ROOT, '.dw', 'config', 'config.schema.json');
|
|
32
|
-
let schema = loadSchema(schemaPath) || loadSchema(fallbackSchemaPath);
|
|
33
|
-
|
|
34
|
-
if (!schema) {
|
|
35
|
-
warn('Schema file not found — skipping schema validation');
|
|
36
|
-
warn('Expected at: .dw/config/config.schema.json');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
41
|
-
const validate = ajv.compile(schema);
|
|
42
|
-
const valid = validate(config);
|
|
43
|
-
|
|
44
|
-
if (valid) {
|
|
45
|
-
ok('Schema validation passed');
|
|
46
|
-
} else {
|
|
47
|
-
err('Schema validation failed:');
|
|
48
|
-
for (const error of validate.errors) {
|
|
49
|
-
const path = error.instancePath || '(root)';
|
|
50
|
-
log(` ${path}: ${error.message}`);
|
|
51
|
-
if (error.params?.allowedValues) {
|
|
52
|
-
log(` Allowed: ${error.params.allowedValues.join(', ')}`);
|
|
53
|
-
}
|
|
54
|
-
if (error.params?.additionalProperty) {
|
|
55
|
-
log(` Unknown key: ${error.params.additionalProperty}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
info('Semantic checks');
|
|
62
|
-
runSemanticChecks(config);
|
|
63
|
-
|
|
64
|
-
console.log();
|
|
65
|
-
ok('Config is valid.');
|
|
66
|
-
console.log();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function runSemanticChecks(config) {
|
|
70
|
-
const warnings = [];
|
|
71
|
-
|
|
72
|
-
if (!config.project?.name || config.project.name === 'my-project') {
|
|
73
|
-
warnings.push('project.name is still "my-project" — consider updating');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (config.quality?.block_on_fail && !config.quality?.test_command && !config.quality?.lint_command) {
|
|
77
|
-
warnings.push('quality.block_on_fail is true but no test_command or lint_command configured');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (config.tracking?.estimation && config.workflow?.default_depth === 'quick') {
|
|
81
|
-
warnings.push('tracking.estimation is enabled but depth is "quick" — estimation may add unnecessary overhead');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const roles = config.team?.roles || [];
|
|
85
|
-
if (roles.includes('pm') && !config.tracking?.log_work) {
|
|
86
|
-
warnings.push('PM role enabled but tracking.log_work is false — PM dashboard needs work logs');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (roles.includes('qc') && !config.quality?.test_command) {
|
|
90
|
-
warnings.push('QC role enabled but no quality.test_command — QC workflow benefits from tests');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (config.claude?.worktree_execution && config.workflow?.default_depth === 'quick') {
|
|
94
|
-
warnings.push('claude.worktree_execution is true with "quick" depth — worktree is typically for thorough workflows');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (warnings.length === 0) {
|
|
98
|
-
ok('No semantic issues found');
|
|
99
|
-
} else {
|
|
100
|
-
for (const w of warnings) warn(w);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import Ajv from 'ajv';
|
|
5
|
+
import { header, ok, warn, err, info, log } from '../lib/ui.mjs';
|
|
6
|
+
import { loadConfig, loadSchema } from '../lib/config.mjs';
|
|
7
|
+
|
|
8
|
+
const TOOLKIT_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
9
|
+
|
|
10
|
+
export async function validateCommand(opts) {
|
|
11
|
+
const configPath = resolve(opts.file);
|
|
12
|
+
|
|
13
|
+
header('dw-kit Config Validation');
|
|
14
|
+
|
|
15
|
+
if (!existsSync(configPath)) {
|
|
16
|
+
err(`Config file not found: ${configPath}`);
|
|
17
|
+
log('Run `dw init` to create a config file.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
info(`Loading ${opts.file}`);
|
|
22
|
+
const config = loadConfig(configPath);
|
|
23
|
+
if (!config) {
|
|
24
|
+
err('Failed to parse YAML config.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
ok('YAML syntax valid');
|
|
28
|
+
|
|
29
|
+
info('Validating against schema');
|
|
30
|
+
const schemaPath = resolve('.dw', 'config', 'config.schema.json');
|
|
31
|
+
const fallbackSchemaPath = join(TOOLKIT_ROOT, '.dw', 'config', 'config.schema.json');
|
|
32
|
+
let schema = loadSchema(schemaPath) || loadSchema(fallbackSchemaPath);
|
|
33
|
+
|
|
34
|
+
if (!schema) {
|
|
35
|
+
warn('Schema file not found — skipping schema validation');
|
|
36
|
+
warn('Expected at: .dw/config/config.schema.json');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
41
|
+
const validate = ajv.compile(schema);
|
|
42
|
+
const valid = validate(config);
|
|
43
|
+
|
|
44
|
+
if (valid) {
|
|
45
|
+
ok('Schema validation passed');
|
|
46
|
+
} else {
|
|
47
|
+
err('Schema validation failed:');
|
|
48
|
+
for (const error of validate.errors) {
|
|
49
|
+
const path = error.instancePath || '(root)';
|
|
50
|
+
log(` ${path}: ${error.message}`);
|
|
51
|
+
if (error.params?.allowedValues) {
|
|
52
|
+
log(` Allowed: ${error.params.allowedValues.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
if (error.params?.additionalProperty) {
|
|
55
|
+
log(` Unknown key: ${error.params.additionalProperty}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
info('Semantic checks');
|
|
62
|
+
runSemanticChecks(config);
|
|
63
|
+
|
|
64
|
+
console.log();
|
|
65
|
+
ok('Config is valid.');
|
|
66
|
+
console.log();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function runSemanticChecks(config) {
|
|
70
|
+
const warnings = [];
|
|
71
|
+
|
|
72
|
+
if (!config.project?.name || config.project.name === 'my-project') {
|
|
73
|
+
warnings.push('project.name is still "my-project" — consider updating');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (config.quality?.block_on_fail && !config.quality?.test_command && !config.quality?.lint_command) {
|
|
77
|
+
warnings.push('quality.block_on_fail is true but no test_command or lint_command configured');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (config.tracking?.estimation && config.workflow?.default_depth === 'quick') {
|
|
81
|
+
warnings.push('tracking.estimation is enabled but depth is "quick" — estimation may add unnecessary overhead');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const roles = config.team?.roles || [];
|
|
85
|
+
if (roles.includes('pm') && !config.tracking?.log_work) {
|
|
86
|
+
warnings.push('PM role enabled but tracking.log_work is false — PM dashboard needs work logs');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (roles.includes('qc') && !config.quality?.test_command) {
|
|
90
|
+
warnings.push('QC role enabled but no quality.test_command — QC workflow benefits from tests');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (config.claude?.worktree_execution && config.workflow?.default_depth === 'quick') {
|
|
94
|
+
warnings.push('claude.worktree_execution is true with "quick" depth — worktree is typically for thorough workflows');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (warnings.length === 0) {
|
|
98
|
+
ok('No semantic issues found');
|
|
99
|
+
} else {
|
|
100
|
+
for (const w of warnings) warn(w);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/lib/clipboard.mjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { platform } from 'node:process';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Copy text to system clipboard.
|
|
6
|
-
* @returns {boolean} true if succeeded
|
|
7
|
-
*/
|
|
8
|
-
export function copyToClipboard(text) {
|
|
9
|
-
const candidates = platform === 'win32'
|
|
10
|
-
? [['clip']]
|
|
11
|
-
: platform === 'darwin'
|
|
12
|
-
? [['pbcopy']]
|
|
13
|
-
: [['wl-copy'], ['xclip', '-selection', 'clipboard'], ['xsel', '--clipboard', '--input']];
|
|
14
|
-
|
|
15
|
-
for (const [cmd, ...args] of candidates) {
|
|
16
|
-
try {
|
|
17
|
-
const result = spawnSync(cmd, args, { input: text, encoding: 'utf-8' });
|
|
18
|
-
if (result.status === 0) return true;
|
|
19
|
-
} catch {
|
|
20
|
-
// Try next candidate.
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { platform } from 'node:process';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Copy text to system clipboard.
|
|
6
|
+
* @returns {boolean} true if succeeded
|
|
7
|
+
*/
|
|
8
|
+
export function copyToClipboard(text) {
|
|
9
|
+
const candidates = platform === 'win32'
|
|
10
|
+
? [['clip']]
|
|
11
|
+
: platform === 'darwin'
|
|
12
|
+
? [['pbcopy']]
|
|
13
|
+
: [['wl-copy'], ['xclip', '-selection', 'clipboard'], ['xsel', '--clipboard', '--input']];
|
|
14
|
+
|
|
15
|
+
for (const [cmd, ...args] of candidates) {
|
|
16
|
+
try {
|
|
17
|
+
const result = spawnSync(cmd, args, { input: text, encoding: 'utf-8' });
|
|
18
|
+
if (result.status === 0) return true;
|
|
19
|
+
} catch {
|
|
20
|
+
// Try next candidate.
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
package/src/lib/goal-store.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { parseFrontmatter, stringifyFrontmatter } from './frontmatter.mjs';
|
|
4
|
+
import { parseSubtaskTracker } from './timeline-parser.mjs';
|
|
4
5
|
|
|
5
6
|
const GOALS_DIR = '.dw/goals';
|
|
6
7
|
const INDEX_FILE = '.dw/goals/goals-index.json';
|
|
@@ -145,7 +146,7 @@ export function computeGoalProgress(goalId, rootDir = process.cwd(), linkedTasks
|
|
|
145
146
|
if (!existsSync(taskFile)) continue;
|
|
146
147
|
const content = readFileSync(taskFile, 'utf8');
|
|
147
148
|
const tracker = extractTrackerSection(content);
|
|
148
|
-
for (const row of
|
|
149
|
+
for (const row of parseSubtaskTracker(tracker)) {
|
|
149
150
|
total++;
|
|
150
151
|
if (row.status.includes('✅') || /Done/i.test(row.status)) done++;
|
|
151
152
|
else if (row.status.includes('🟡') || /In Progress/i.test(row.status)) inProgress++;
|
|
@@ -163,19 +164,6 @@ function extractTrackerSection(content) {
|
|
|
163
164
|
return m ? m[0] : '';
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
function parseTrackerRows(section) {
|
|
167
|
-
const rows = [];
|
|
168
|
-
const lines = section.split('\n');
|
|
169
|
-
for (const line of lines) {
|
|
170
|
-
// Match table rows like: | ST-N | Description | Status | Date | Notes |
|
|
171
|
-
if (!/^\|\s*(ST|WS)-/.test(line)) continue;
|
|
172
|
-
const cells = line.split('|').map((c) => c.trim());
|
|
173
|
-
if (cells.length < 4) continue;
|
|
174
|
-
// cells[0] = "" (before first |), [1] = ID, [2] = subtask, [3] = status
|
|
175
|
-
rows.push({ id: cells[1], subtask: cells[2], status: cells[3] || '', date: cells[4] || '', notes: cells[5] || '' });
|
|
176
|
-
}
|
|
177
|
-
return rows;
|
|
178
|
-
}
|
|
179
167
|
|
|
180
168
|
export function removeIndexEntry(goalId, rootDir = process.cwd()) {
|
|
181
169
|
const index = readGoalIndex(rootDir);
|
package/src/lib/platform.mjs
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Detect which AI development platform is available.
|
|
7
|
-
* Returns: 'claude-cli' | 'cursor' | 'generic'
|
|
8
|
-
*/
|
|
9
|
-
export function detectPlatform(projectDir = process.cwd()) {
|
|
10
|
-
if (isClaudeCliAvailable()) return 'claude-cli';
|
|
11
|
-
if (isCursorProject(projectDir)) return 'cursor';
|
|
12
|
-
return 'generic';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function isClaudeCliAvailable() {
|
|
16
|
-
try {
|
|
17
|
-
execSync('claude --version', { stdio: 'pipe', timeout: 5000 });
|
|
18
|
-
return true;
|
|
19
|
-
} catch {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isCursorProject(projectDir) {
|
|
25
|
-
return (
|
|
26
|
-
existsSync(join(projectDir, '.cursor')) ||
|
|
27
|
-
existsSync(join(projectDir, '.cursorrc')) ||
|
|
28
|
-
process.env.CURSOR_SESSION_ID != null
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function platformLabel(platform) {
|
|
33
|
-
const labels = {
|
|
34
|
-
'claude-cli': 'Claude Code CLI',
|
|
35
|
-
'cursor': 'Cursor IDE',
|
|
36
|
-
'generic': 'Generic (AGENT.md)',
|
|
37
|
-
};
|
|
38
|
-
return labels[platform] || platform;
|
|
39
|
-
}
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect which AI development platform is available.
|
|
7
|
+
* Returns: 'claude-cli' | 'cursor' | 'generic'
|
|
8
|
+
*/
|
|
9
|
+
export function detectPlatform(projectDir = process.cwd()) {
|
|
10
|
+
if (isClaudeCliAvailable()) return 'claude-cli';
|
|
11
|
+
if (isCursorProject(projectDir)) return 'cursor';
|
|
12
|
+
return 'generic';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isClaudeCliAvailable() {
|
|
16
|
+
try {
|
|
17
|
+
execSync('claude --version', { stdio: 'pipe', timeout: 5000 });
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isCursorProject(projectDir) {
|
|
25
|
+
return (
|
|
26
|
+
existsSync(join(projectDir, '.cursor')) ||
|
|
27
|
+
existsSync(join(projectDir, '.cursorrc')) ||
|
|
28
|
+
process.env.CURSOR_SESSION_ID != null
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function platformLabel(platform) {
|
|
33
|
+
const labels = {
|
|
34
|
+
'claude-cli': 'Claude Code CLI',
|
|
35
|
+
'cursor': 'Cursor IDE',
|
|
36
|
+
'generic': 'Generic (AGENT.md)',
|
|
37
|
+
};
|
|
38
|
+
return labels[platform] || platform;
|
|
39
|
+
}
|