@fledge/workflow 0.4.0 → 0.6.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/dist/scripts/brief.js +127 -75
- package/package.json +2 -2
- package/skills/brief/SKILL.md +17 -16
package/dist/scripts/brief.js
CHANGED
|
@@ -3679,81 +3679,98 @@ const tasksFrontmatter = object({ tasks: array(object({
|
|
|
3679
3679
|
//#region ../cli/dist/brief.js
|
|
3680
3680
|
const BRIEFS_DIRECTORY = path.join(".fledge", "briefs");
|
|
3681
3681
|
/**
|
|
3682
|
-
*
|
|
3682
|
+
* Creates a BriefContext from a project directory path or falls back to `cwd()`.
|
|
3683
3683
|
*
|
|
3684
|
-
* @
|
|
3684
|
+
* @param projectDirectory - Optional project root override.
|
|
3685
|
+
* @returns A BriefContext with the resolved project root.
|
|
3685
3686
|
*/
|
|
3686
|
-
function
|
|
3687
|
-
return path.
|
|
3687
|
+
function createBriefContext(projectDirectory) {
|
|
3688
|
+
return { projectRoot: projectDirectory ? path.resolve(projectDirectory) : cwd() };
|
|
3689
|
+
}
|
|
3690
|
+
/**
|
|
3691
|
+
* Returns the absolute path to the briefs directory for the given context.
|
|
3692
|
+
*
|
|
3693
|
+
* @param context - The brief context.
|
|
3694
|
+
* @returns The absolute path to `.fledge/briefs/` in the project root.
|
|
3695
|
+
*/
|
|
3696
|
+
function getBriefsDirectory(context) {
|
|
3697
|
+
return path.join(context.projectRoot, BRIEFS_DIRECTORY);
|
|
3688
3698
|
}
|
|
3689
3699
|
/**
|
|
3690
3700
|
* Returns the absolute path to a specific brief's directory.
|
|
3691
3701
|
*
|
|
3702
|
+
* @param context - The brief context.
|
|
3692
3703
|
* @param name - The brief name (used as directory name).
|
|
3693
3704
|
* @returns The absolute path to the brief directory.
|
|
3694
3705
|
*/
|
|
3695
|
-
function getBriefDirectory(name) {
|
|
3696
|
-
return path.join(getBriefsDirectory(), name);
|
|
3706
|
+
function getBriefDirectory(context, name) {
|
|
3707
|
+
return path.join(getBriefsDirectory(context), name);
|
|
3697
3708
|
}
|
|
3698
3709
|
/**
|
|
3699
3710
|
* Returns the absolute path to a brief's `brief.md` file.
|
|
3700
3711
|
*
|
|
3712
|
+
* @param context - The brief context.
|
|
3701
3713
|
* @param name - The brief name.
|
|
3702
3714
|
* @returns The absolute path to `brief.md`.
|
|
3703
3715
|
*/
|
|
3704
|
-
function getBriefFile(name) {
|
|
3705
|
-
return path.join(getBriefDirectory(name), "brief.md");
|
|
3716
|
+
function getBriefFile(context, name) {
|
|
3717
|
+
return path.join(getBriefDirectory(context, name), "brief.md");
|
|
3706
3718
|
}
|
|
3707
3719
|
/**
|
|
3708
3720
|
* Returns the absolute path to a brief's `tasks.md` file.
|
|
3709
3721
|
*
|
|
3722
|
+
* @param context - The brief context.
|
|
3710
3723
|
* @param name - The brief name.
|
|
3711
3724
|
* @returns The absolute path to `tasks.md`.
|
|
3712
3725
|
*/
|
|
3713
|
-
function getTasksFile(name) {
|
|
3714
|
-
return path.join(getBriefDirectory(name), "tasks.md");
|
|
3726
|
+
function getTasksFile(context, name) {
|
|
3727
|
+
return path.join(getBriefDirectory(context, name), "tasks.md");
|
|
3715
3728
|
}
|
|
3716
3729
|
/**
|
|
3717
3730
|
* Checks whether a brief directory exists.
|
|
3718
3731
|
*
|
|
3732
|
+
* @param context - The brief context.
|
|
3719
3733
|
* @param name - The brief name.
|
|
3720
3734
|
* @returns `true` if the brief directory exists.
|
|
3721
3735
|
*/
|
|
3722
|
-
function briefExists(name) {
|
|
3723
|
-
return fs.existsSync(getBriefDirectory(name));
|
|
3736
|
+
function briefExists(context, name) {
|
|
3737
|
+
return fs.existsSync(getBriefDirectory(context, name));
|
|
3724
3738
|
}
|
|
3725
3739
|
/**
|
|
3726
3740
|
* Reads and validates the frontmatter of a brief's `brief.md` file.
|
|
3727
3741
|
*
|
|
3742
|
+
* @param context - The brief context.
|
|
3728
3743
|
* @param name - The brief name.
|
|
3729
3744
|
* @returns The validated brief frontmatter.
|
|
3730
3745
|
* @throws If the file does not exist or frontmatter fails validation.
|
|
3731
3746
|
*/
|
|
3732
|
-
function readBriefFrontmatter(name) {
|
|
3733
|
-
const content = fs.readFileSync(getBriefFile(name), "utf8");
|
|
3747
|
+
function readBriefFrontmatter(context, name) {
|
|
3748
|
+
const content = fs.readFileSync(getBriefFile(context, name), "utf8");
|
|
3734
3749
|
return briefFrontmatter.parse(parseFrontmatter(content));
|
|
3735
3750
|
}
|
|
3736
3751
|
/**
|
|
3737
3752
|
* Reads and validates the frontmatter of a brief's `tasks.md` file.
|
|
3738
3753
|
*
|
|
3754
|
+
* @param context - The brief context.
|
|
3739
3755
|
* @param name - The brief name.
|
|
3740
3756
|
* @returns The validated tasks frontmatter.
|
|
3741
3757
|
* @throws If the file does not exist or frontmatter fails validation.
|
|
3742
3758
|
*/
|
|
3743
|
-
function readTasksFrontmatter(name) {
|
|
3744
|
-
const content = fs.readFileSync(getTasksFile(name), "utf8");
|
|
3759
|
+
function readTasksFrontmatter(context, name) {
|
|
3760
|
+
const content = fs.readFileSync(getTasksFile(context, name), "utf8");
|
|
3745
3761
|
return tasksFrontmatter.parse(parseFrontmatter(content));
|
|
3746
3762
|
}
|
|
3747
3763
|
/**
|
|
3748
3764
|
* Updates the frontmatter of a brief's `brief.md` file while preserving its body content.
|
|
3749
3765
|
*
|
|
3766
|
+
* @param context - The brief context.
|
|
3750
3767
|
* @param name - The brief name.
|
|
3751
3768
|
* @param data - The new frontmatter data to write.
|
|
3752
3769
|
*/
|
|
3753
|
-
function updateBriefFrontmatter(name, data) {
|
|
3754
|
-
const
|
|
3755
|
-
const content = fs.readFileSync(
|
|
3756
|
-
fs.writeFileSync(
|
|
3770
|
+
function updateBriefFrontmatter(context, name, data) {
|
|
3771
|
+
const file = getBriefFile(context, name);
|
|
3772
|
+
const content = fs.readFileSync(file, "utf8");
|
|
3773
|
+
fs.writeFileSync(file, writeFrontmatter(data, content));
|
|
3757
3774
|
}
|
|
3758
3775
|
/**
|
|
3759
3776
|
* Validates that a state transition is allowed and throws a descriptive error if not.
|
|
@@ -3780,37 +3797,52 @@ function formatDate() {
|
|
|
3780
3797
|
/**
|
|
3781
3798
|
* Lists all brief directories in the project.
|
|
3782
3799
|
*
|
|
3800
|
+
* @param context - The brief context.
|
|
3783
3801
|
* @returns An array of brief names (directory names).
|
|
3784
3802
|
*/
|
|
3785
|
-
function listBriefs() {
|
|
3786
|
-
const directory = getBriefsDirectory();
|
|
3803
|
+
function listBriefs(context) {
|
|
3804
|
+
const directory = getBriefsDirectory(context);
|
|
3787
3805
|
if (!fs.existsSync(directory)) return [];
|
|
3788
3806
|
return fs.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
3789
3807
|
}
|
|
3790
3808
|
//#endregion
|
|
3809
|
+
//#region ../cli/dist/commands/brief/shared.js
|
|
3810
|
+
/**
|
|
3811
|
+
* Shared argument definition for the `--project-dir` flag used by all brief subcommands.
|
|
3812
|
+
*/
|
|
3813
|
+
const projectDirectory = {
|
|
3814
|
+
required: false,
|
|
3815
|
+
alias: "project-dir",
|
|
3816
|
+
description: "Project root directory. Overrides cwd() for locating .fledge/briefs/."
|
|
3817
|
+
};
|
|
3818
|
+
//#endregion
|
|
3791
3819
|
//#region ../cli/dist/commands/brief/complete.js
|
|
3792
3820
|
var complete_default = defineCommand({
|
|
3793
3821
|
meta: {
|
|
3794
3822
|
name: "complete",
|
|
3795
3823
|
description: "Transition a brief from active to completed"
|
|
3796
3824
|
},
|
|
3797
|
-
args: {
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3825
|
+
args: {
|
|
3826
|
+
name: {
|
|
3827
|
+
type: "positional",
|
|
3828
|
+
required: true,
|
|
3829
|
+
description: "The name of the brief to complete"
|
|
3830
|
+
},
|
|
3831
|
+
projectDirectory
|
|
3832
|
+
},
|
|
3802
3833
|
run(context) {
|
|
3834
|
+
const ctx = createBriefContext(context.args.projectDirectory);
|
|
3803
3835
|
const { name } = context.args;
|
|
3804
|
-
if (!briefExists(name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(name)}"`);
|
|
3805
|
-
const brief = readBriefFrontmatter(name);
|
|
3836
|
+
if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
|
|
3837
|
+
const brief = readBriefFrontmatter(ctx, name);
|
|
3806
3838
|
validateTransition(brief.status, "completed");
|
|
3807
3839
|
if (!brief.summary) throw new Error("Brief must have a summary before completing. Add a \"summary\" field to the brief.md frontmatter.");
|
|
3808
|
-
const incomplete = readTasksFrontmatter(name).tasks.filter((task) => !task.done);
|
|
3840
|
+
const incomplete = readTasksFrontmatter(ctx, name).tasks.filter((task) => !task.done);
|
|
3809
3841
|
if (incomplete.length > 0) {
|
|
3810
3842
|
const names = incomplete.map((task) => ` - ${task.name}`).join("\n");
|
|
3811
3843
|
throw new Error(`Cannot complete brief with ${incomplete.length} incomplete task(s):\n${names}`);
|
|
3812
3844
|
}
|
|
3813
|
-
updateBriefFrontmatter(name, {
|
|
3845
|
+
updateBriefFrontmatter(ctx, name, {
|
|
3814
3846
|
...brief,
|
|
3815
3847
|
status: "completed",
|
|
3816
3848
|
updated: formatDate()
|
|
@@ -3825,24 +3857,28 @@ var create_default = defineCommand({
|
|
|
3825
3857
|
name: "create",
|
|
3826
3858
|
description: "Create a new brief with stub files"
|
|
3827
3859
|
},
|
|
3828
|
-
args: {
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3860
|
+
args: {
|
|
3861
|
+
name: {
|
|
3862
|
+
type: "positional",
|
|
3863
|
+
required: true,
|
|
3864
|
+
description: "The name of the brief to create (used as directory name)"
|
|
3865
|
+
},
|
|
3866
|
+
projectDirectory
|
|
3867
|
+
},
|
|
3833
3868
|
run(context) {
|
|
3869
|
+
const ctx = createBriefContext(context.args.projectDirectory);
|
|
3834
3870
|
const { name } = context.args;
|
|
3835
|
-
if (briefExists(name)) throw new Error(`Brief "${name}" already exists at "${getBriefDirectory(name)}"`);
|
|
3836
|
-
const directory = getBriefDirectory(name);
|
|
3871
|
+
if (briefExists(ctx, name)) throw new Error(`Brief "${name}" already exists at "${getBriefDirectory(ctx, name)}"`);
|
|
3872
|
+
const directory = getBriefDirectory(ctx, name);
|
|
3837
3873
|
fs.mkdirSync(directory, { recursive: true });
|
|
3838
3874
|
const date = formatDate();
|
|
3839
|
-
fs.writeFileSync(getBriefFile(name), writeFrontmatter({
|
|
3875
|
+
fs.writeFileSync(getBriefFile(ctx, name), writeFrontmatter({
|
|
3840
3876
|
name,
|
|
3841
3877
|
status: "draft",
|
|
3842
3878
|
created: date,
|
|
3843
3879
|
updated: date
|
|
3844
3880
|
}));
|
|
3845
|
-
fs.writeFileSync(getTasksFile(name), writeFrontmatter({ tasks: [] }));
|
|
3881
|
+
fs.writeFileSync(getTasksFile(ctx, name), writeFrontmatter({ tasks: [] }));
|
|
3846
3882
|
stdout.write(`Created brief "${name}" at "${directory}"\n`);
|
|
3847
3883
|
}
|
|
3848
3884
|
});
|
|
@@ -3853,20 +3889,24 @@ var list_default = defineCommand({
|
|
|
3853
3889
|
name: "list",
|
|
3854
3890
|
description: "List all briefs with their status and progress"
|
|
3855
3891
|
},
|
|
3856
|
-
args: {
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3892
|
+
args: {
|
|
3893
|
+
status: {
|
|
3894
|
+
type: "string",
|
|
3895
|
+
required: false,
|
|
3896
|
+
description: "Filter by brief status (draft, active, completed)"
|
|
3897
|
+
},
|
|
3898
|
+
projectDirectory
|
|
3899
|
+
},
|
|
3861
3900
|
run(context) {
|
|
3862
|
-
const
|
|
3901
|
+
const ctx = createBriefContext(context.args.projectDirectory);
|
|
3902
|
+
const names = listBriefs(ctx);
|
|
3863
3903
|
if (names.length === 0) {
|
|
3864
3904
|
stdout.write("No briefs found\n");
|
|
3865
3905
|
return;
|
|
3866
3906
|
}
|
|
3867
3907
|
let briefs = names.map((name) => {
|
|
3868
|
-
const brief = readBriefFrontmatter(name);
|
|
3869
|
-
const { tasks } = readTasksFrontmatter(name);
|
|
3908
|
+
const brief = readBriefFrontmatter(ctx, name);
|
|
3909
|
+
const { tasks } = readTasksFrontmatter(ctx, name);
|
|
3870
3910
|
const done = tasks.filter((task) => task.done).length;
|
|
3871
3911
|
return {
|
|
3872
3912
|
...brief,
|
|
@@ -3916,18 +3956,22 @@ var start_default = defineCommand({
|
|
|
3916
3956
|
name: "start",
|
|
3917
3957
|
description: "Transition a brief from draft to active"
|
|
3918
3958
|
},
|
|
3919
|
-
args: {
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3959
|
+
args: {
|
|
3960
|
+
name: {
|
|
3961
|
+
type: "positional",
|
|
3962
|
+
required: true,
|
|
3963
|
+
description: "The name of the brief to start"
|
|
3964
|
+
},
|
|
3965
|
+
projectDirectory
|
|
3966
|
+
},
|
|
3924
3967
|
run(context) {
|
|
3968
|
+
const ctx = createBriefContext(context.args.projectDirectory);
|
|
3925
3969
|
const { name } = context.args;
|
|
3926
|
-
if (!briefExists(name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(name)}"`);
|
|
3927
|
-
const brief = readBriefFrontmatter(name);
|
|
3970
|
+
if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
|
|
3971
|
+
const brief = readBriefFrontmatter(ctx, name);
|
|
3928
3972
|
validateTransition(brief.status, "active");
|
|
3929
|
-
if (readTasksFrontmatter(name).tasks.length === 0) throw new Error("Brief must have at least one task before starting");
|
|
3930
|
-
updateBriefFrontmatter(name, {
|
|
3973
|
+
if (readTasksFrontmatter(ctx, name).tasks.length === 0) throw new Error("Brief must have at least one task before starting");
|
|
3974
|
+
updateBriefFrontmatter(ctx, name, {
|
|
3931
3975
|
...brief,
|
|
3932
3976
|
status: "active",
|
|
3933
3977
|
updated: formatDate()
|
|
@@ -3980,24 +4024,28 @@ runMain(defineCommand({
|
|
|
3980
4024
|
name: "status",
|
|
3981
4025
|
description: "Show the status and task progress of a brief"
|
|
3982
4026
|
},
|
|
3983
|
-
args: {
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
4027
|
+
args: {
|
|
4028
|
+
name: {
|
|
4029
|
+
type: "positional",
|
|
4030
|
+
required: true,
|
|
4031
|
+
description: "The name of the brief to show status for"
|
|
4032
|
+
},
|
|
4033
|
+
projectDirectory
|
|
4034
|
+
},
|
|
3988
4035
|
run(context) {
|
|
4036
|
+
const ctx = createBriefContext(context.args.projectDirectory);
|
|
3989
4037
|
const { name } = context.args;
|
|
3990
|
-
if (!briefExists(name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(name)}"`);
|
|
3991
|
-
const brief = readBriefFrontmatter(name);
|
|
3992
|
-
const { tasks } = readTasksFrontmatter(name);
|
|
4038
|
+
if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
|
|
4039
|
+
const brief = readBriefFrontmatter(ctx, name);
|
|
4040
|
+
const { tasks } = readTasksFrontmatter(ctx, name);
|
|
3993
4041
|
const done = tasks.filter((task) => task.done).length;
|
|
3994
4042
|
const lines = [];
|
|
3995
4043
|
lines.push(`${name} [${brief.status}] ${done}/${tasks.length} tasks done`);
|
|
3996
4044
|
lines.push("");
|
|
3997
4045
|
const groups = groupTasks(tasks);
|
|
3998
|
-
for (const [group,
|
|
4046
|
+
for (const [group, groupedTasks] of groups) {
|
|
3999
4047
|
if (group) lines.push(group);
|
|
4000
|
-
for (const task of
|
|
4048
|
+
for (const task of groupedTasks) lines.push(formatTask(task));
|
|
4001
4049
|
}
|
|
4002
4050
|
stdout.write(`${lines.join("\n")}\n`);
|
|
4003
4051
|
}
|
|
@@ -4007,19 +4055,23 @@ runMain(defineCommand({
|
|
|
4007
4055
|
name: "validate",
|
|
4008
4056
|
description: "Validate brief and tasks files against their schemas"
|
|
4009
4057
|
},
|
|
4010
|
-
args: {
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4058
|
+
args: {
|
|
4059
|
+
name: {
|
|
4060
|
+
type: "positional",
|
|
4061
|
+
required: true,
|
|
4062
|
+
description: "The name of the brief to validate"
|
|
4063
|
+
},
|
|
4064
|
+
projectDirectory
|
|
4065
|
+
},
|
|
4015
4066
|
run(context) {
|
|
4067
|
+
const ctx = createBriefContext(context.args.projectDirectory);
|
|
4016
4068
|
const { name } = context.args;
|
|
4017
|
-
if (!briefExists(name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(name)}"`);
|
|
4069
|
+
if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
|
|
4018
4070
|
const errors = [];
|
|
4019
|
-
const briefContent = fs.readFileSync(getBriefFile(name), "utf8");
|
|
4071
|
+
const briefContent = fs.readFileSync(getBriefFile(ctx, name), "utf8");
|
|
4020
4072
|
const briefResult = briefFrontmatter.safeParse(parseFrontmatter(briefContent));
|
|
4021
4073
|
if (!briefResult.success) for (const issue of briefResult.error.issues) errors.push(`brief.md: ${issue.path.join(".")}: ${issue.message}`);
|
|
4022
|
-
const tasksContent = fs.readFileSync(getTasksFile(name), "utf8");
|
|
4074
|
+
const tasksContent = fs.readFileSync(getTasksFile(ctx, name), "utf8");
|
|
4023
4075
|
const tasksResult = tasksFrontmatter.safeParse(parseFrontmatter(tasksContent));
|
|
4024
4076
|
if (!tasksResult.success) for (const issue of tasksResult.error.issues) errors.push(`tasks.md: ${issue.path.join(".")}: ${issue.message}`);
|
|
4025
4077
|
if (errors.length > 0) throw new Error(`Validation failed for "${name}":\n${errors.map((error) => ` - ${error}`).join("\n")}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fledge/workflow",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"author": "René Schapka",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"files": [
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"node": ">=24"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@fledge/cli": "^0.
|
|
18
|
+
"@fledge/cli": "^0.9.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@antfu/eslint-config": "7.7.3",
|
package/skills/brief/SKILL.md
CHANGED
|
@@ -5,18 +5,19 @@ description: >-
|
|
|
5
5
|
Invoked directly via /fledge-brief, not auto-triggered.
|
|
6
6
|
metadata:
|
|
7
7
|
type: workflow
|
|
8
|
+
allowed-tools: Bash(scripts/brief.js *)
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
## Available scripts
|
|
11
12
|
|
|
12
|
-
Self-contained scripts bundled with this skill.
|
|
13
|
+
Self-contained executable scripts bundled with this skill. All brief commands require `--project-dir` to point to the project root:
|
|
13
14
|
|
|
14
|
-
- **`scripts/brief.js create <name>`** -- Create a new brief with stub files
|
|
15
|
-
- **`scripts/brief.js start <name>`** -- Transition a brief from draft to active
|
|
16
|
-
- **`scripts/brief.js complete <name>`** -- Transition a brief from active to completed
|
|
17
|
-
- **`scripts/brief.js status <name>`** -- Show status and task progress
|
|
18
|
-
- **`scripts/brief.js list [--status <status>]
|
|
19
|
-
- **`scripts/brief.js validate <name>`** -- Validate brief files against schemas
|
|
15
|
+
- **`scripts/brief.js create <name> --project-dir <path>`** -- Create a new brief with stub files
|
|
16
|
+
- **`scripts/brief.js start <name> --project-dir <path>`** -- Transition a brief from draft to active
|
|
17
|
+
- **`scripts/brief.js complete <name> --project-dir <path>`** -- Transition a brief from active to completed
|
|
18
|
+
- **`scripts/brief.js status <name> --project-dir <path>`** -- Show status and task progress
|
|
19
|
+
- **`scripts/brief.js list [--status <status>] --project-dir <path>`** -- List all briefs with progress and summary
|
|
20
|
+
- **`scripts/brief.js validate <name> --project-dir <path>`** -- Validate brief files against schemas
|
|
20
21
|
- **`scripts/brief.js schema`** -- Output JSON Schema for brief and tasks frontmatter
|
|
21
22
|
|
|
22
23
|
## Step 0: Determine intent
|
|
@@ -27,7 +28,7 @@ Ask what the user wants to do, or infer from context. Present these options:
|
|
|
27
28
|
2. **Continue a brief** -- pick up an existing brief. Proceed to Step 4.
|
|
28
29
|
3. **Complete a brief** -- wrap up a finished feature. Proceed to Step 5.
|
|
29
30
|
|
|
30
|
-
If unclear, run `
|
|
31
|
+
If unclear, run `scripts/brief.js list --project-dir <path>` to show current briefs and ask.
|
|
31
32
|
|
|
32
33
|
---
|
|
33
34
|
|
|
@@ -35,7 +36,7 @@ If unclear, run `node scripts/brief.js list` to show current briefs and ask.
|
|
|
35
36
|
|
|
36
37
|
Before writing anything, build an understanding of what exists.
|
|
37
38
|
|
|
38
|
-
1. Run `
|
|
39
|
+
1. Run `scripts/brief.js list --status completed --project-dir <path>` to read summaries of completed features. Note anything relevant to the new feature.
|
|
39
40
|
2. Ask the user what they want to build. Keep it conversational, not a form. Aim to understand:
|
|
40
41
|
- What is the user-facing change?
|
|
41
42
|
- Why does it matter?
|
|
@@ -50,7 +51,7 @@ Proceed to Step 2.
|
|
|
50
51
|
|
|
51
52
|
## Step 2: Draft the brief
|
|
52
53
|
|
|
53
|
-
Run `
|
|
54
|
+
Run `scripts/brief.js create <name> --project-dir <path>` to create the brief directory.
|
|
54
55
|
|
|
55
56
|
Write the brief content into `brief.md`. The frontmatter is managed by the scripts. The markdown body should capture:
|
|
56
57
|
|
|
@@ -86,7 +87,7 @@ tasks:
|
|
|
86
87
|
|
|
87
88
|
Order tasks by dependency: tasks that others depend on come first within their group.
|
|
88
89
|
|
|
89
|
-
After writing tasks, run `
|
|
90
|
+
After writing tasks, run `scripts/brief.js validate <name> --project-dir <path>` to confirm the brief is valid, then run `scripts/brief.js start <name> --project-dir <path>` to transition to active.
|
|
90
91
|
|
|
91
92
|
Present the complete brief and task list to the user for review before starting.
|
|
92
93
|
|
|
@@ -94,22 +95,22 @@ Present the complete brief and task list to the user for review before starting.
|
|
|
94
95
|
|
|
95
96
|
## Step 4: Continue a brief
|
|
96
97
|
|
|
97
|
-
Run `
|
|
98
|
+
Run `scripts/brief.js list --project-dir <path>` to show all briefs. If the user does not specify which brief, ask them to pick one.
|
|
98
99
|
|
|
99
|
-
Run `
|
|
100
|
+
Run `scripts/brief.js status <name> --project-dir <path>` to show progress. Read the brief and tasks files to understand the full context.
|
|
100
101
|
|
|
101
102
|
From here, the user may want to:
|
|
102
103
|
- **Discuss a task** -- talk through approach before implementing
|
|
103
104
|
- **Update tasks** -- mark tasks as done, add new tasks, reorder
|
|
104
105
|
- **Revise the brief** -- update scope or design decisions based on what was learned during implementation
|
|
105
106
|
|
|
106
|
-
When updating task status, modify the `tasks.md` frontmatter directly, then run `
|
|
107
|
+
When updating task status, modify the `tasks.md` frontmatter directly, then run `scripts/brief.js status <name> --project-dir <path>` to confirm the update.
|
|
107
108
|
|
|
108
109
|
---
|
|
109
110
|
|
|
110
111
|
## Step 5: Complete a brief
|
|
111
112
|
|
|
112
|
-
Run `
|
|
113
|
+
Run `scripts/brief.js status <name> --project-dir <path>` to verify all tasks are done.
|
|
113
114
|
|
|
114
115
|
If there are incomplete tasks, ask the user whether to:
|
|
115
116
|
1. Mark remaining tasks as done (if they were completed outside this conversation)
|
|
@@ -120,4 +121,4 @@ Write a summary into the `brief.md` frontmatter `summary` field. The summary sho
|
|
|
120
121
|
- What was built
|
|
121
122
|
- Key decisions or patterns established that future features should know about
|
|
122
123
|
|
|
123
|
-
Run `
|
|
124
|
+
Run `scripts/brief.js complete <name> --project-dir <path>` to transition to completed. The script validates that all tasks are done and the summary is present.
|