thachvd-kit 1.0.38 → 1.0.39
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 -21
- package/README.md +63 -1
- package/THIRD_PARTY_NOTICES.md +49 -49
- package/bin/spec-doctor.js +2 -1
- package/bin/spec-site.js +639 -0
- package/bin/spec.js +43 -1
- package/bin/upgrade.js +303 -303
- package/package.json +3 -3
- package/skills/finishing-a-development-branch/SKILL.md +240 -240
- package/skills/requesting-code-review/code-reviewer.md +198 -198
- package/skills/subagent-driven-development/SKILL.md +574 -574
- package/skills/subagent-driven-development/implementer-prompt.md +154 -154
- package/skills/subagent-driven-development/re-review-prompt.md +115 -115
- package/skills/subagent-driven-development/scripts/review-package +53 -53
- package/skills/subagent-driven-development/scripts/review-package.js +52 -52
- package/skills/subagent-driven-development/scripts/sdd-workspace +82 -82
- package/skills/subagent-driven-development/scripts/sdd-workspace-lib.js +62 -62
- package/skills/subagent-driven-development/scripts/sdd-workspace.js +15 -15
- package/skills/subagent-driven-development/scripts/task-brief +43 -43
- package/skills/subagent-driven-development/scripts/task-brief.js +46 -46
- package/skills/subagent-driven-development/task-reviewer-prompt.md +207 -207
- package/skills/upstream.json +30 -30
- package/skills/using-git-worktrees/SKILL.md +175 -175
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { runGit, workspaceForPlan } = require('./sdd-workspace-lib');
|
|
6
|
-
|
|
7
|
-
function gitStatus(args) {
|
|
8
|
-
const { spawnSync } = require('child_process');
|
|
9
|
-
return spawnSync('git', args, { encoding: 'utf8' });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function main() {
|
|
13
|
-
if (process.argv.length < 5 || process.argv.length > 6) {
|
|
14
|
-
throw new Error('usage: review-package PLAN_FILE BASE HEAD [OUTFILE]');
|
|
15
|
-
}
|
|
16
|
-
const [, , planFile, base, head, explicitOutput] = process.argv;
|
|
17
|
-
if (!fs.existsSync(path.resolve(planFile))) throw new Error(`no such plan file: ${planFile}`);
|
|
18
|
-
for (const ref of [base, head]) {
|
|
19
|
-
const result = gitStatus(['rev-parse', '--verify', '--quiet', ref]);
|
|
20
|
-
if (result.status !== 0) throw new Error(`bad ${ref === base ? 'BASE' : 'HEAD'}: ${ref}`);
|
|
21
|
-
}
|
|
22
|
-
const ancestor = gitStatus(['merge-base', '--is-ancestor', base, head]);
|
|
23
|
-
if (ancestor.status !== 0) throw new Error(`HEAD is not a descendant of BASE: ${base}..${head}`);
|
|
24
|
-
const commits = Number(runGit(['rev-list', '--count', `${base}..${head}`]));
|
|
25
|
-
if (commits <= 0) throw new Error(`empty commit range: ${base}..${head}`);
|
|
26
|
-
|
|
27
|
-
const outputPath = explicitOutput || path.join(
|
|
28
|
-
workspaceForPlan(planFile),
|
|
29
|
-
`review-${runGit(['rev-parse', '--short', base])}..${runGit(['rev-parse', '--short', head])}.diff`
|
|
30
|
-
);
|
|
31
|
-
const content = [
|
|
32
|
-
`# Review package: ${base}..${head}`,
|
|
33
|
-
'',
|
|
34
|
-
'## Commits',
|
|
35
|
-
runGit(['log', '--oneline', `${base}..${head}`]),
|
|
36
|
-
'',
|
|
37
|
-
'## Files changed',
|
|
38
|
-
runGit(['diff', '--stat', `${base}..${head}`]),
|
|
39
|
-
'',
|
|
40
|
-
'## Diff',
|
|
41
|
-
runGit(['diff', '-U10', `${base}..${head}`])
|
|
42
|
-
].join('\n') + '\n';
|
|
43
|
-
fs.writeFileSync(outputPath, content);
|
|
44
|
-
console.log(`wrote ${outputPath}: ${commits} commit(s), ${Buffer.byteLength(content)} bytes`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
main();
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error(error.message);
|
|
51
|
-
process.exitCode = 2;
|
|
52
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { runGit, workspaceForPlan } = require('./sdd-workspace-lib');
|
|
6
|
+
|
|
7
|
+
function gitStatus(args) {
|
|
8
|
+
const { spawnSync } = require('child_process');
|
|
9
|
+
return spawnSync('git', args, { encoding: 'utf8' });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function main() {
|
|
13
|
+
if (process.argv.length < 5 || process.argv.length > 6) {
|
|
14
|
+
throw new Error('usage: review-package PLAN_FILE BASE HEAD [OUTFILE]');
|
|
15
|
+
}
|
|
16
|
+
const [, , planFile, base, head, explicitOutput] = process.argv;
|
|
17
|
+
if (!fs.existsSync(path.resolve(planFile))) throw new Error(`no such plan file: ${planFile}`);
|
|
18
|
+
for (const ref of [base, head]) {
|
|
19
|
+
const result = gitStatus(['rev-parse', '--verify', '--quiet', ref]);
|
|
20
|
+
if (result.status !== 0) throw new Error(`bad ${ref === base ? 'BASE' : 'HEAD'}: ${ref}`);
|
|
21
|
+
}
|
|
22
|
+
const ancestor = gitStatus(['merge-base', '--is-ancestor', base, head]);
|
|
23
|
+
if (ancestor.status !== 0) throw new Error(`HEAD is not a descendant of BASE: ${base}..${head}`);
|
|
24
|
+
const commits = Number(runGit(['rev-list', '--count', `${base}..${head}`]));
|
|
25
|
+
if (commits <= 0) throw new Error(`empty commit range: ${base}..${head}`);
|
|
26
|
+
|
|
27
|
+
const outputPath = explicitOutput || path.join(
|
|
28
|
+
workspaceForPlan(planFile),
|
|
29
|
+
`review-${runGit(['rev-parse', '--short', base])}..${runGit(['rev-parse', '--short', head])}.diff`
|
|
30
|
+
);
|
|
31
|
+
const content = [
|
|
32
|
+
`# Review package: ${base}..${head}`,
|
|
33
|
+
'',
|
|
34
|
+
'## Commits',
|
|
35
|
+
runGit(['log', '--oneline', `${base}..${head}`]),
|
|
36
|
+
'',
|
|
37
|
+
'## Files changed',
|
|
38
|
+
runGit(['diff', '--stat', `${base}..${head}`]),
|
|
39
|
+
'',
|
|
40
|
+
'## Diff',
|
|
41
|
+
runGit(['diff', '-U10', `${base}..${head}`])
|
|
42
|
+
].join('\n') + '\n';
|
|
43
|
+
fs.writeFileSync(outputPath, content);
|
|
44
|
+
console.log(`wrote ${outputPath}: ${commits} commit(s), ${Buffer.byteLength(content)} bytes`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
main();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(error.message);
|
|
51
|
+
process.exitCode = 2;
|
|
52
|
+
}
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Resolve and ensure the working-tree directory SDD uses for one plan's
|
|
3
|
-
# short-lived artifacts: task briefs, implementer reports, review packages,
|
|
4
|
-
# and the progress ledger. Print the plan directory's absolute path.
|
|
5
|
-
#
|
|
6
|
-
# One directory per plan (.superpowers/sdd/<plan-basename>/) so a follow-up
|
|
7
|
-
# plan in the same working tree can never read or overwrite another plan's
|
|
8
|
-
# artifacts. A stale ledger misread as current progress makes controllers
|
|
9
|
-
# skip whole task sequences — plan-scoping removes that failure structurally.
|
|
10
|
-
#
|
|
11
|
-
# Basename slugs collide when two plans share a filename (docs/alpha/plan.md
|
|
12
|
-
# vs docs/beta/plan.md), so each workspace records its owning plan's path in
|
|
13
|
-
# a plan-path marker (repo-relative in-repo, absolute outside). A workspace
|
|
14
|
-
# owned by a different plan is skipped and the slug disambiguated with the
|
|
15
|
-
# plan's parent-directory name, then a counter. A workspace with no marker
|
|
16
|
-
# predates the marker scheme and is adopted for the current plan so in-flight
|
|
17
|
-
# workspaces keep resolving — which means the first collision on such a
|
|
18
|
-
# legacy workspace adopts instead of detecting; acceptable, marker-less
|
|
19
|
-
# workspaces age out as plans finish.
|
|
20
|
-
#
|
|
21
|
-
# The workspace lives in the working tree (not under .git/) because Claude Code
|
|
22
|
-
# treats .git/ as a protected path and denies agent writes there — which blocks
|
|
23
|
-
# an implementer subagent from writing its report file. A self-ignoring
|
|
24
|
-
# .gitignore at .superpowers/sdd/ keeps every plan's workspace out of
|
|
25
|
-
# `git status` and out of accidental commits without modifying any tracked file.
|
|
26
|
-
#
|
|
27
|
-
# Single source of truth for the workspace location, so task-brief and
|
|
28
|
-
# review-package cannot drift to different directories.
|
|
29
|
-
#
|
|
30
|
-
# Usage: sdd-workspace PLAN_FILE
|
|
31
|
-
set -euo pipefail
|
|
32
|
-
|
|
33
|
-
if [ $# -ne 1 ]; then
|
|
34
|
-
echo "usage: sdd-workspace PLAN_FILE" >&2
|
|
35
|
-
exit 2
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
plan=$1
|
|
39
|
-
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
|
|
40
|
-
|
|
41
|
-
slug=$(basename "$plan" .md)
|
|
42
|
-
[ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \
|
|
43
|
-
|| { echo "cannot derive a workspace name from: $plan" >&2; exit 2; }
|
|
44
|
-
|
|
45
|
-
root=$(git rev-parse --show-toplevel)
|
|
46
|
-
base="$root/.superpowers/sdd"
|
|
47
|
-
|
|
48
|
-
# Normalize the plan path (physical directory, so relative/absolute/../
|
|
49
|
-
# spellings of one plan compare equal) and express it as the marker value:
|
|
50
|
-
# repo-relative when the plan lives under the repo root, absolute otherwise.
|
|
51
|
-
plan_dir=$(CDPATH= cd -- "$(dirname "$plan")" && pwd -P)
|
|
52
|
-
plan_abs="$plan_dir/$(basename "$plan")"
|
|
53
|
-
case "$plan_abs" in
|
|
54
|
-
"$root"/*) plan_id=${plan_abs#"$root"/} ;;
|
|
55
|
-
*) plan_id=$plan_abs ;;
|
|
56
|
-
esac
|
|
57
|
-
|
|
58
|
-
# True when the workspace at $1 is (or becomes) this plan's: an existing
|
|
59
|
-
# marker must name this plan; a missing marker means a new workspace or a
|
|
60
|
-
# pre-marker legacy one, and either way the plan claims it by writing one.
|
|
61
|
-
owns() {
|
|
62
|
-
if [ -e "$1/plan-path" ]; then
|
|
63
|
-
[ "$(cat "$1/plan-path")" = "$plan_id" ]
|
|
64
|
-
else
|
|
65
|
-
mkdir -p "$1"
|
|
66
|
-
printf '%s\n' "$plan_id" > "$1/plan-path"
|
|
67
|
-
fi
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
dir="$base/$slug"
|
|
71
|
-
if ! owns "$dir"; then
|
|
72
|
-
parent=$(basename "$plan_dir")
|
|
73
|
-
dir="$base/$slug-$parent"
|
|
74
|
-
if ! owns "$dir"; then
|
|
75
|
-
n=2
|
|
76
|
-
while ! owns "$base/$slug-$parent-$n"; do n=$((n + 1)); done
|
|
77
|
-
dir="$base/$slug-$parent-$n"
|
|
78
|
-
fi
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
printf '*\n' > "$base/.gitignore"
|
|
82
|
-
CDPATH= cd -- "$dir" && pwd
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve and ensure the working-tree directory SDD uses for one plan's
|
|
3
|
+
# short-lived artifacts: task briefs, implementer reports, review packages,
|
|
4
|
+
# and the progress ledger. Print the plan directory's absolute path.
|
|
5
|
+
#
|
|
6
|
+
# One directory per plan (.superpowers/sdd/<plan-basename>/) so a follow-up
|
|
7
|
+
# plan in the same working tree can never read or overwrite another plan's
|
|
8
|
+
# artifacts. A stale ledger misread as current progress makes controllers
|
|
9
|
+
# skip whole task sequences — plan-scoping removes that failure structurally.
|
|
10
|
+
#
|
|
11
|
+
# Basename slugs collide when two plans share a filename (docs/alpha/plan.md
|
|
12
|
+
# vs docs/beta/plan.md), so each workspace records its owning plan's path in
|
|
13
|
+
# a plan-path marker (repo-relative in-repo, absolute outside). A workspace
|
|
14
|
+
# owned by a different plan is skipped and the slug disambiguated with the
|
|
15
|
+
# plan's parent-directory name, then a counter. A workspace with no marker
|
|
16
|
+
# predates the marker scheme and is adopted for the current plan so in-flight
|
|
17
|
+
# workspaces keep resolving — which means the first collision on such a
|
|
18
|
+
# legacy workspace adopts instead of detecting; acceptable, marker-less
|
|
19
|
+
# workspaces age out as plans finish.
|
|
20
|
+
#
|
|
21
|
+
# The workspace lives in the working tree (not under .git/) because Claude Code
|
|
22
|
+
# treats .git/ as a protected path and denies agent writes there — which blocks
|
|
23
|
+
# an implementer subagent from writing its report file. A self-ignoring
|
|
24
|
+
# .gitignore at .superpowers/sdd/ keeps every plan's workspace out of
|
|
25
|
+
# `git status` and out of accidental commits without modifying any tracked file.
|
|
26
|
+
#
|
|
27
|
+
# Single source of truth for the workspace location, so task-brief and
|
|
28
|
+
# review-package cannot drift to different directories.
|
|
29
|
+
#
|
|
30
|
+
# Usage: sdd-workspace PLAN_FILE
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
if [ $# -ne 1 ]; then
|
|
34
|
+
echo "usage: sdd-workspace PLAN_FILE" >&2
|
|
35
|
+
exit 2
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
plan=$1
|
|
39
|
+
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
|
|
40
|
+
|
|
41
|
+
slug=$(basename "$plan" .md)
|
|
42
|
+
[ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \
|
|
43
|
+
|| { echo "cannot derive a workspace name from: $plan" >&2; exit 2; }
|
|
44
|
+
|
|
45
|
+
root=$(git rev-parse --show-toplevel)
|
|
46
|
+
base="$root/.superpowers/sdd"
|
|
47
|
+
|
|
48
|
+
# Normalize the plan path (physical directory, so relative/absolute/../
|
|
49
|
+
# spellings of one plan compare equal) and express it as the marker value:
|
|
50
|
+
# repo-relative when the plan lives under the repo root, absolute otherwise.
|
|
51
|
+
plan_dir=$(CDPATH= cd -- "$(dirname "$plan")" && pwd -P)
|
|
52
|
+
plan_abs="$plan_dir/$(basename "$plan")"
|
|
53
|
+
case "$plan_abs" in
|
|
54
|
+
"$root"/*) plan_id=${plan_abs#"$root"/} ;;
|
|
55
|
+
*) plan_id=$plan_abs ;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
# True when the workspace at $1 is (or becomes) this plan's: an existing
|
|
59
|
+
# marker must name this plan; a missing marker means a new workspace or a
|
|
60
|
+
# pre-marker legacy one, and either way the plan claims it by writing one.
|
|
61
|
+
owns() {
|
|
62
|
+
if [ -e "$1/plan-path" ]; then
|
|
63
|
+
[ "$(cat "$1/plan-path")" = "$plan_id" ]
|
|
64
|
+
else
|
|
65
|
+
mkdir -p "$1"
|
|
66
|
+
printf '%s\n' "$plan_id" > "$1/plan-path"
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
dir="$base/$slug"
|
|
71
|
+
if ! owns "$dir"; then
|
|
72
|
+
parent=$(basename "$plan_dir")
|
|
73
|
+
dir="$base/$slug-$parent"
|
|
74
|
+
if ! owns "$dir"; then
|
|
75
|
+
n=2
|
|
76
|
+
while ! owns "$base/$slug-$parent-$n"; do n=$((n + 1)); done
|
|
77
|
+
dir="$base/$slug-$parent-$n"
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
printf '*\n' > "$base/.gitignore"
|
|
82
|
+
CDPATH= cd -- "$dir" && pwd
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { spawnSync } = require('child_process');
|
|
4
|
-
|
|
5
|
-
function runGit(args, cwd = process.cwd()) {
|
|
6
|
-
const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
7
|
-
if (result.error || result.status !== 0) {
|
|
8
|
-
const detail = (result.stderr || result.error?.message || '').trim();
|
|
9
|
-
throw new Error(detail || `git ${args.join(' ')} failed`);
|
|
10
|
-
}
|
|
11
|
-
return result.stdout.trim();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function assertPlan(planFile) {
|
|
15
|
-
const planPath = path.resolve(planFile);
|
|
16
|
-
if (!fs.existsSync(planPath) || !fs.statSync(planPath).isFile()) {
|
|
17
|
-
throw new Error(`no such plan file: ${planFile}`);
|
|
18
|
-
}
|
|
19
|
-
return planPath;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function workspaceForPlan(planFile) {
|
|
23
|
-
const planPath = assertPlan(planFile);
|
|
24
|
-
const root = runGit(['rev-parse', '--show-toplevel']);
|
|
25
|
-
const base = path.join(root, '.superpowers', 'sdd');
|
|
26
|
-
const slug = path.basename(planPath, path.extname(planPath));
|
|
27
|
-
if (!slug || slug === '.' || slug === '..') {
|
|
28
|
-
throw new Error(`cannot derive a workspace name from: ${planFile}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const relative = path.relative(root, planPath).replace(/\\/g, '/');
|
|
32
|
-
const planId = relative && !relative.startsWith('../') && relative !== '..' && !path.isAbsolute(relative)
|
|
33
|
-
? relative
|
|
34
|
-
: planPath.replace(/\\/g, '/');
|
|
35
|
-
|
|
36
|
-
function owns(candidate) {
|
|
37
|
-
const marker = path.join(candidate, 'plan-path');
|
|
38
|
-
if (fs.existsSync(marker)) {
|
|
39
|
-
return fs.readFileSync(marker, 'utf8').trim() === planId;
|
|
40
|
-
}
|
|
41
|
-
fs.mkdirSync(candidate, { recursive: true });
|
|
42
|
-
fs.writeFileSync(marker, `${planId}\n`);
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let directory = path.join(base, slug);
|
|
47
|
-
if (!owns(directory)) {
|
|
48
|
-
const parent = path.basename(path.dirname(planPath));
|
|
49
|
-
directory = path.join(base, `${slug}-${parent}`);
|
|
50
|
-
if (!owns(directory)) {
|
|
51
|
-
let n = 2;
|
|
52
|
-
while (!owns(path.join(base, `${slug}-${parent}-${n}`))) n += 1;
|
|
53
|
-
directory = path.join(base, `${slug}-${parent}-${n}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
fs.mkdirSync(base, { recursive: true });
|
|
58
|
-
fs.writeFileSync(path.join(base, '.gitignore'), '*\n');
|
|
59
|
-
return path.resolve(directory);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
module.exports = { assertPlan, runGit, workspaceForPlan };
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
function runGit(args, cwd = process.cwd()) {
|
|
6
|
+
const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
7
|
+
if (result.error || result.status !== 0) {
|
|
8
|
+
const detail = (result.stderr || result.error?.message || '').trim();
|
|
9
|
+
throw new Error(detail || `git ${args.join(' ')} failed`);
|
|
10
|
+
}
|
|
11
|
+
return result.stdout.trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function assertPlan(planFile) {
|
|
15
|
+
const planPath = path.resolve(planFile);
|
|
16
|
+
if (!fs.existsSync(planPath) || !fs.statSync(planPath).isFile()) {
|
|
17
|
+
throw new Error(`no such plan file: ${planFile}`);
|
|
18
|
+
}
|
|
19
|
+
return planPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function workspaceForPlan(planFile) {
|
|
23
|
+
const planPath = assertPlan(planFile);
|
|
24
|
+
const root = runGit(['rev-parse', '--show-toplevel']);
|
|
25
|
+
const base = path.join(root, '.superpowers', 'sdd');
|
|
26
|
+
const slug = path.basename(planPath, path.extname(planPath));
|
|
27
|
+
if (!slug || slug === '.' || slug === '..') {
|
|
28
|
+
throw new Error(`cannot derive a workspace name from: ${planFile}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const relative = path.relative(root, planPath).replace(/\\/g, '/');
|
|
32
|
+
const planId = relative && !relative.startsWith('../') && relative !== '..' && !path.isAbsolute(relative)
|
|
33
|
+
? relative
|
|
34
|
+
: planPath.replace(/\\/g, '/');
|
|
35
|
+
|
|
36
|
+
function owns(candidate) {
|
|
37
|
+
const marker = path.join(candidate, 'plan-path');
|
|
38
|
+
if (fs.existsSync(marker)) {
|
|
39
|
+
return fs.readFileSync(marker, 'utf8').trim() === planId;
|
|
40
|
+
}
|
|
41
|
+
fs.mkdirSync(candidate, { recursive: true });
|
|
42
|
+
fs.writeFileSync(marker, `${planId}\n`);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let directory = path.join(base, slug);
|
|
47
|
+
if (!owns(directory)) {
|
|
48
|
+
const parent = path.basename(path.dirname(planPath));
|
|
49
|
+
directory = path.join(base, `${slug}-${parent}`);
|
|
50
|
+
if (!owns(directory)) {
|
|
51
|
+
let n = 2;
|
|
52
|
+
while (!owns(path.join(base, `${slug}-${parent}-${n}`))) n += 1;
|
|
53
|
+
directory = path.join(base, `${slug}-${parent}-${n}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fs.mkdirSync(base, { recursive: true });
|
|
58
|
+
fs.writeFileSync(path.join(base, '.gitignore'), '*\n');
|
|
59
|
+
return path.resolve(directory);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { assertPlan, runGit, workspaceForPlan };
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { workspaceForPlan } = require('./sdd-workspace-lib');
|
|
4
|
-
|
|
5
|
-
function main() {
|
|
6
|
-
if (process.argv.length !== 3) throw new Error('usage: sdd-workspace PLAN_FILE');
|
|
7
|
-
console.log(workspaceForPlan(process.argv[2]));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
main();
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error(error.message);
|
|
14
|
-
process.exitCode = 2;
|
|
15
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { workspaceForPlan } = require('./sdd-workspace-lib');
|
|
4
|
+
|
|
5
|
+
function main() {
|
|
6
|
+
if (process.argv.length !== 3) throw new Error('usage: sdd-workspace PLAN_FILE');
|
|
7
|
+
console.log(workspaceForPlan(process.argv[2]));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
main();
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(error.message);
|
|
14
|
+
process.exitCode = 2;
|
|
15
|
+
}
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Extract one task's full text from an implementation plan into a file the
|
|
3
|
-
# implementer reads in one call, so the task text never has to be pasted
|
|
4
|
-
# through the controller's context.
|
|
5
|
-
#
|
|
6
|
-
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
|
|
7
|
-
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
|
|
8
|
-
# (per plan and per worktree; concurrent runs of the SAME plan in the same
|
|
9
|
-
# working tree share it).
|
|
10
|
-
set -euo pipefail
|
|
11
|
-
|
|
12
|
-
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
|
13
|
-
echo "usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]" >&2
|
|
14
|
-
exit 2
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
plan=$1
|
|
18
|
-
n=$2
|
|
19
|
-
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
|
|
20
|
-
|
|
21
|
-
if [ $# -eq 3 ]; then
|
|
22
|
-
out=$3
|
|
23
|
-
else
|
|
24
|
-
# Invoke via bash rather than direct exec: some extractors (Python zipfile)
|
|
25
|
-
# strip Unix exec bits when unpacking marketplace packages (#2040).
|
|
26
|
-
dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
|
|
27
|
-
out="$dir/task-${n}-brief.md"
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
awk -v n="$n" '
|
|
31
|
-
/^```/ { infence = !infence }
|
|
32
|
-
!infence && /^#+[ \t]+Task[ \t]+[0-9]+/ {
|
|
33
|
-
intask = ($0 ~ ("^#+[ \t]+Task[ \t]+" n "([^0-9]|$)"))
|
|
34
|
-
}
|
|
35
|
-
intask { print }
|
|
36
|
-
' "$plan" > "$out"
|
|
37
|
-
|
|
38
|
-
if [ ! -s "$out" ]; then
|
|
39
|
-
echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2
|
|
40
|
-
exit 3
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
echo "wrote ${out}: $(wc -l < "$out" | tr -d ' ') lines"
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Extract one task's full text from an implementation plan into a file the
|
|
3
|
+
# implementer reads in one call, so the task text never has to be pasted
|
|
4
|
+
# through the controller's context.
|
|
5
|
+
#
|
|
6
|
+
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
|
|
7
|
+
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
|
|
8
|
+
# (per plan and per worktree; concurrent runs of the SAME plan in the same
|
|
9
|
+
# working tree share it).
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
|
13
|
+
echo "usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]" >&2
|
|
14
|
+
exit 2
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
plan=$1
|
|
18
|
+
n=$2
|
|
19
|
+
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
|
|
20
|
+
|
|
21
|
+
if [ $# -eq 3 ]; then
|
|
22
|
+
out=$3
|
|
23
|
+
else
|
|
24
|
+
# Invoke via bash rather than direct exec: some extractors (Python zipfile)
|
|
25
|
+
# strip Unix exec bits when unpacking marketplace packages (#2040).
|
|
26
|
+
dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
|
|
27
|
+
out="$dir/task-${n}-brief.md"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
awk -v n="$n" '
|
|
31
|
+
/^```/ { infence = !infence }
|
|
32
|
+
!infence && /^#+[ \t]+Task[ \t]+[0-9]+/ {
|
|
33
|
+
intask = ($0 ~ ("^#+[ \t]+Task[ \t]+" n "([^0-9]|$)"))
|
|
34
|
+
}
|
|
35
|
+
intask { print }
|
|
36
|
+
' "$plan" > "$out"
|
|
37
|
+
|
|
38
|
+
if [ ! -s "$out" ]; then
|
|
39
|
+
echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2
|
|
40
|
+
exit 3
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo "wrote ${out}: $(wc -l < "$out" | tr -d ' ') lines"
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { workspaceForPlan } = require('./sdd-workspace-lib');
|
|
6
|
-
|
|
7
|
-
function extractTask(planText, taskNumber) {
|
|
8
|
-
const lines = planText.split(/\r?\n/);
|
|
9
|
-
const selected = [];
|
|
10
|
-
let inFence = false;
|
|
11
|
-
let active = false;
|
|
12
|
-
for (const line of lines) {
|
|
13
|
-
if (line.startsWith('```')) {
|
|
14
|
-
inFence = !inFence;
|
|
15
|
-
} else if (!inFence && /^#+[ \t]+Task[ \t]+[0-9]+/.test(line)) {
|
|
16
|
-
const match = line.match(/^#+[ \t]+Task[ \t]+([0-9]+)(?:[^0-9]|$)/);
|
|
17
|
-
active = Boolean(match && Number(match[1]) === taskNumber);
|
|
18
|
-
}
|
|
19
|
-
if (active) selected.push(line);
|
|
20
|
-
}
|
|
21
|
-
return selected;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function main() {
|
|
25
|
-
if (process.argv.length < 4 || process.argv.length > 5) {
|
|
26
|
-
throw new Error('usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]');
|
|
27
|
-
}
|
|
28
|
-
const planFile = process.argv[2];
|
|
29
|
-
const taskNumber = Number(process.argv[3]);
|
|
30
|
-
if (!Number.isInteger(taskNumber) || taskNumber < 1) throw new Error('TASK_NUMBER must be a positive integer');
|
|
31
|
-
const planPath = path.resolve(planFile);
|
|
32
|
-
if (!fs.existsSync(planPath)) throw new Error(`no such plan file: ${planFile}`);
|
|
33
|
-
const outputPath = process.argv[4] || path.join(workspaceForPlan(planFile), `task-${taskNumber}-brief.md`);
|
|
34
|
-
const selected = extractTask(fs.readFileSync(planPath, 'utf8'), taskNumber);
|
|
35
|
-
if (selected.length === 0) throw new Error(`task ${taskNumber} not found in ${planFile} (no heading matching 'Task ${taskNumber}')`);
|
|
36
|
-
const content = `${selected.join('\n')}\n`;
|
|
37
|
-
fs.writeFileSync(outputPath, content);
|
|
38
|
-
console.log(`wrote ${outputPath}: ${content.split(/\r?\n/).length - 1} lines`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
main();
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error(error.message);
|
|
45
|
-
process.exitCode = 2;
|
|
46
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { workspaceForPlan } = require('./sdd-workspace-lib');
|
|
6
|
+
|
|
7
|
+
function extractTask(planText, taskNumber) {
|
|
8
|
+
const lines = planText.split(/\r?\n/);
|
|
9
|
+
const selected = [];
|
|
10
|
+
let inFence = false;
|
|
11
|
+
let active = false;
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
if (line.startsWith('```')) {
|
|
14
|
+
inFence = !inFence;
|
|
15
|
+
} else if (!inFence && /^#+[ \t]+Task[ \t]+[0-9]+/.test(line)) {
|
|
16
|
+
const match = line.match(/^#+[ \t]+Task[ \t]+([0-9]+)(?:[^0-9]|$)/);
|
|
17
|
+
active = Boolean(match && Number(match[1]) === taskNumber);
|
|
18
|
+
}
|
|
19
|
+
if (active) selected.push(line);
|
|
20
|
+
}
|
|
21
|
+
return selected;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function main() {
|
|
25
|
+
if (process.argv.length < 4 || process.argv.length > 5) {
|
|
26
|
+
throw new Error('usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]');
|
|
27
|
+
}
|
|
28
|
+
const planFile = process.argv[2];
|
|
29
|
+
const taskNumber = Number(process.argv[3]);
|
|
30
|
+
if (!Number.isInteger(taskNumber) || taskNumber < 1) throw new Error('TASK_NUMBER must be a positive integer');
|
|
31
|
+
const planPath = path.resolve(planFile);
|
|
32
|
+
if (!fs.existsSync(planPath)) throw new Error(`no such plan file: ${planFile}`);
|
|
33
|
+
const outputPath = process.argv[4] || path.join(workspaceForPlan(planFile), `task-${taskNumber}-brief.md`);
|
|
34
|
+
const selected = extractTask(fs.readFileSync(planPath, 'utf8'), taskNumber);
|
|
35
|
+
if (selected.length === 0) throw new Error(`task ${taskNumber} not found in ${planFile} (no heading matching 'Task ${taskNumber}')`);
|
|
36
|
+
const content = `${selected.join('\n')}\n`;
|
|
37
|
+
fs.writeFileSync(outputPath, content);
|
|
38
|
+
console.log(`wrote ${outputPath}: ${content.split(/\r?\n/).length - 1} lines`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
main();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(error.message);
|
|
45
|
+
process.exitCode = 2;
|
|
46
|
+
}
|