@fledge/workflow 0.6.2 → 0.7.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.
@@ -554,7 +554,6 @@ const string$1 = (params) => {
554
554
  const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
555
555
  return new RegExp(`^${regex}$`);
556
556
  };
557
- const boolean$1 = /^(?:true|false)$/i;
558
557
  const lowercase = /^[^A-Z]*$/;
559
558
  const uppercase = /^[^a-z]*$/;
560
559
  //#endregion
@@ -1166,24 +1165,6 @@ const $ZodJWT = /* @__PURE__ */ $constructor("$ZodJWT", (inst, def) => {
1166
1165
  });
1167
1166
  };
1168
1167
  });
1169
- const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
1170
- $ZodType.init(inst, def);
1171
- inst._zod.pattern = boolean$1;
1172
- inst._zod.parse = (payload, _ctx) => {
1173
- if (def.coerce) try {
1174
- payload.value = Boolean(payload.value);
1175
- } catch (_) {}
1176
- const input = payload.value;
1177
- if (typeof input === "boolean") return payload;
1178
- payload.issues.push({
1179
- expected: "boolean",
1180
- code: "invalid_type",
1181
- input,
1182
- inst
1183
- });
1184
- return payload;
1185
- };
1186
- });
1187
1168
  const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
1188
1169
  $ZodType.init(inst, def);
1189
1170
  inst._zod.parse = (payload) => payload;
@@ -2146,13 +2127,6 @@ function _isoDuration(Class, params) {
2146
2127
  });
2147
2128
  }
2148
2129
  /* @__NO_SIDE_EFFECTS__ */
2149
- function _boolean(Class, params) {
2150
- return new Class({
2151
- type: "boolean",
2152
- ...normalizeParams(params)
2153
- });
2154
- }
2155
- /* @__NO_SIDE_EFFECTS__ */
2156
2130
  function _unknown(Class) {
2157
2131
  return new Class({ type: "unknown" });
2158
2132
  }
@@ -3336,14 +3310,6 @@ const ZodJWT = /* @__PURE__ */ $constructor("ZodJWT", (inst, def) => {
3336
3310
  $ZodJWT.init(inst, def);
3337
3311
  ZodStringFormat.init(inst, def);
3338
3312
  });
3339
- const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
3340
- $ZodBoolean.init(inst, def);
3341
- ZodType.init(inst, def);
3342
- inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
3343
- });
3344
- function boolean(params) {
3345
- return /* @__PURE__ */ _boolean(ZodBoolean, params);
3346
- }
3347
3313
  const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
3348
3314
  $ZodUnknown.init(inst, def);
3349
3315
  ZodType.init(inst, def);
@@ -3650,8 +3616,10 @@ function superRefine(fn) {
3650
3616
  //#region ../cli/dist/schemas/brief.js
3651
3617
  const briefStatus = _enum([
3652
3618
  "draft",
3619
+ "ready",
3653
3620
  "active",
3654
- "completed"
3621
+ "completed",
3622
+ "cancelled"
3655
3623
  ]);
3656
3624
  const briefFrontmatter = object({
3657
3625
  name: string().min(1),
@@ -3663,16 +3631,36 @@ const briefFrontmatter = object({
3663
3631
  /**
3664
3632
  * Valid state transitions for a brief.
3665
3633
  * Each key maps to the set of states it can transition to.
3634
+ *
3635
+ * draft → ready: brief skill marks the brief as designed and enriched
3636
+ * ready → active: implement skill begins implementation
3637
+ * ready → draft: brief needs rework
3638
+ * active → completed: implement skill finishes all tasks
3639
+ * draft/ready/active → cancelled: feature is dropped
3666
3640
  */
3667
3641
  const briefTransitions = {
3668
- draft: ["active"],
3669
- active: ["completed", "draft"],
3670
- completed: []
3642
+ draft: ["ready", "cancelled"],
3643
+ ready: [
3644
+ "active",
3645
+ "draft",
3646
+ "cancelled"
3647
+ ],
3648
+ active: ["completed", "cancelled"],
3649
+ completed: [],
3650
+ cancelled: []
3671
3651
  };
3652
+ //#endregion
3653
+ //#region ../cli/dist/schemas/tasks.js
3654
+ const taskStatus = _enum([
3655
+ "pending",
3656
+ "active",
3657
+ "completed",
3658
+ "skipped"
3659
+ ]);
3672
3660
  const tasksFrontmatter = object({ tasks: array(object({
3673
3661
  name: string().min(1),
3674
3662
  group: string().min(1).optional(),
3675
- done: boolean().default(false)
3663
+ status: taskStatus.default("pending")
3676
3664
  })) });
3677
3665
  //#endregion
3678
3666
  //#region ../cli/dist/brief.js
@@ -3726,6 +3714,16 @@ function getTasksFile(context, name) {
3726
3714
  return path.join(getBriefDirectory(context, name), "tasks.md");
3727
3715
  }
3728
3716
  /**
3717
+ * Returns the absolute path to a brief's `spec.md` file.
3718
+ *
3719
+ * @param context - The brief context.
3720
+ * @param name - The brief name.
3721
+ * @returns The absolute path to `spec.md`.
3722
+ */
3723
+ function getSpecFile(context, name) {
3724
+ return path.join(getBriefDirectory(context, name), "spec.md");
3725
+ }
3726
+ /**
3729
3727
  * Checks whether a brief directory exists.
3730
3728
  *
3731
3729
  * @param context - The brief context.
@@ -3815,6 +3813,35 @@ const projectDirectory = {
3815
3813
  description: "Project root directory. Overrides cwd() for locating .fledge/briefs/."
3816
3814
  };
3817
3815
  //#endregion
3816
+ //#region ../cli/dist/commands/brief/cancel.js
3817
+ var cancel_default = defineCommand({
3818
+ meta: {
3819
+ name: "cancel",
3820
+ description: "Cancel a brief"
3821
+ },
3822
+ args: {
3823
+ name: {
3824
+ type: "positional",
3825
+ required: true,
3826
+ description: "The name of the brief to cancel"
3827
+ },
3828
+ projectDirectory
3829
+ },
3830
+ run(context) {
3831
+ const ctx = createBriefContext(context.args.projectDirectory);
3832
+ const { name } = context.args;
3833
+ if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
3834
+ const brief = readBriefFrontmatter(ctx, name);
3835
+ validateTransition(brief.status, "cancelled");
3836
+ updateBriefFrontmatter(ctx, name, {
3837
+ ...brief,
3838
+ status: "cancelled",
3839
+ updated: formatDate()
3840
+ });
3841
+ stdout.write(`Brief "${name}" has been cancelled\n`);
3842
+ }
3843
+ });
3844
+ //#endregion
3818
3845
  //#region ../cli/dist/commands/brief/complete.js
3819
3846
  var complete_default = defineCommand({
3820
3847
  meta: {
@@ -3836,10 +3863,10 @@ var complete_default = defineCommand({
3836
3863
  const brief = readBriefFrontmatter(ctx, name);
3837
3864
  validateTransition(brief.status, "completed");
3838
3865
  if (!brief.summary) throw new Error("Brief must have a summary before completing. Add a \"summary\" field to the brief.md frontmatter.");
3839
- const incomplete = readTasksFrontmatter(ctx, name).tasks.filter((task) => !task.done);
3866
+ const incomplete = readTasksFrontmatter(ctx, name).tasks.filter((task) => task.status !== "completed" && task.status !== "skipped");
3840
3867
  if (incomplete.length > 0) {
3841
- const names = incomplete.map((task) => ` - ${task.name}`).join("\n");
3842
- throw new Error(`Cannot complete brief with ${incomplete.length} incomplete task(s):\n${names}`);
3868
+ const names = incomplete.map((task) => ` - ${task.name} [${task.status}]`).join("\n");
3869
+ throw new Error(`Cannot complete brief with ${incomplete.length} unfinished task(s):\n${names}`);
3843
3870
  }
3844
3871
  updateBriefFrontmatter(ctx, name, {
3845
3872
  ...brief,
@@ -3878,6 +3905,7 @@ var create_default = defineCommand({
3878
3905
  updated: date
3879
3906
  }));
3880
3907
  fs.writeFileSync(getTasksFile(ctx, name), writeFrontmatter({ tasks: [] }));
3908
+ fs.writeFileSync(getSpecFile(ctx, name), "");
3881
3909
  stdout.write(`Created brief "${name}" at "${directory}"\n`);
3882
3910
  }
3883
3911
  });
@@ -3892,7 +3920,7 @@ var list_default = defineCommand({
3892
3920
  status: {
3893
3921
  type: "string",
3894
3922
  required: false,
3895
- description: "Filter by brief status (draft, active, completed)"
3923
+ description: "Filter by brief status (draft, ready, active, completed, cancelled)"
3896
3924
  },
3897
3925
  projectDirectory
3898
3926
  },
@@ -3906,7 +3934,7 @@ var list_default = defineCommand({
3906
3934
  let briefs = names.map((name) => {
3907
3935
  const brief = readBriefFrontmatter(ctx, name);
3908
3936
  const { tasks } = readTasksFrontmatter(ctx, name);
3909
- const done = tasks.filter((task) => task.done).length;
3937
+ const done = tasks.filter((task) => task.status === "completed" || task.status === "skipped").length;
3910
3938
  return {
3911
3939
  ...brief,
3912
3940
  tasksDone: done,
@@ -3934,6 +3962,35 @@ var list_default = defineCommand({
3934
3962
  }
3935
3963
  });
3936
3964
  //#endregion
3965
+ //#region ../cli/dist/commands/brief/ready.js
3966
+ var ready_default = defineCommand({
3967
+ meta: {
3968
+ name: "ready",
3969
+ description: "Transition a brief from draft to ready"
3970
+ },
3971
+ args: {
3972
+ name: {
3973
+ type: "positional",
3974
+ required: true,
3975
+ description: "The name of the brief to mark as ready"
3976
+ },
3977
+ projectDirectory
3978
+ },
3979
+ run(context) {
3980
+ const ctx = createBriefContext(context.args.projectDirectory);
3981
+ const { name } = context.args;
3982
+ if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
3983
+ const brief = readBriefFrontmatter(ctx, name);
3984
+ validateTransition(brief.status, "ready");
3985
+ updateBriefFrontmatter(ctx, name, {
3986
+ ...brief,
3987
+ status: "ready",
3988
+ updated: formatDate()
3989
+ });
3990
+ stdout.write(`Brief "${name}" is ready for implementation\n`);
3991
+ }
3992
+ });
3993
+ //#endregion
3937
3994
  //#region ../cli/dist/commands/brief/schema.js
3938
3995
  var schema_default = defineCommand({
3939
3996
  meta: {
@@ -3953,7 +4010,7 @@ var schema_default = defineCommand({
3953
4010
  var start_default = defineCommand({
3954
4011
  meta: {
3955
4012
  name: "start",
3956
- description: "Transition a brief from draft to active"
4013
+ description: "Transition a brief from ready to active"
3957
4014
  },
3958
4015
  args: {
3959
4016
  name: {
@@ -3969,7 +4026,6 @@ var start_default = defineCommand({
3969
4026
  if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
3970
4027
  const brief = readBriefFrontmatter(ctx, name);
3971
4028
  validateTransition(brief.status, "active");
3972
- if (readTasksFrontmatter(ctx, name).tasks.length === 0) throw new Error("Brief must have at least one task before starting");
3973
4029
  updateBriefFrontmatter(ctx, name, {
3974
4030
  ...brief,
3975
4031
  status: "active",
@@ -3997,13 +4053,18 @@ function groupTasks(tasks) {
3997
4053
  return groups;
3998
4054
  }
3999
4055
  /**
4000
- * Formats a task as a line with a checkbox indicator.
4056
+ * Maps a task status to a display indicator.
4001
4057
  *
4002
4058
  * @param task - The task to format.
4003
- * @returns The formatted task string.
4059
+ * @returns The formatted task string with status indicator.
4004
4060
  */
4005
4061
  function formatTask(task) {
4006
- return ` ${task.done ? "[x]" : "[ ]"} ${task.name}`;
4062
+ return ` ${{
4063
+ pending: "[ ]",
4064
+ active: "[~]",
4065
+ completed: "[x]",
4066
+ skipped: "[-]"
4067
+ }[task.status] ?? "[ ]"} ${task.name}`;
4007
4068
  }
4008
4069
  //#endregion
4009
4070
  //#region ../cli/dist/run-brief.js
@@ -4013,9 +4074,11 @@ runMain(defineCommand({
4013
4074
  description: "Manage feature briefs"
4014
4075
  },
4015
4076
  subCommands: {
4077
+ cancel: cancel_default,
4016
4078
  complete: complete_default,
4017
4079
  create: create_default,
4018
4080
  list: list_default,
4081
+ ready: ready_default,
4019
4082
  schema: schema_default,
4020
4083
  start: start_default,
4021
4084
  status: defineCommand({
@@ -4037,7 +4100,7 @@ runMain(defineCommand({
4037
4100
  if (!briefExists(ctx, name)) throw new Error(`Brief "${name}" does not exist at "${getBriefDirectory(ctx, name)}"`);
4038
4101
  const brief = readBriefFrontmatter(ctx, name);
4039
4102
  const { tasks } = readTasksFrontmatter(ctx, name);
4040
- const done = tasks.filter((task) => task.done).length;
4103
+ const done = tasks.filter((task) => task.status === "completed" || task.status === "skipped").length;
4041
4104
  const lines = [];
4042
4105
  lines.push(`${name} [${brief.status}] ${done}/${tasks.length} tasks done`);
4043
4106
  lines.push("");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fledge/workflow",
3
3
  "type": "module",
4
- "version": "0.6.2",
4
+ "version": "0.7.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.9.1"
18
+ "@fledge/cli": "^0.10.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@antfu/eslint-config": "7.7.3",
@@ -1,20 +1,43 @@
1
1
  ---
2
2
  name: fledge-brief
3
3
  description: >-
4
- Guide feature brief creation and lifecycle. Plan new features, create or update feature briefs, break down work into tasks, or complete a feature.
4
+ Guide feature brief creation and lifecycle. Plan new features, create or update feature briefs, enrich with project knowledge, or complete a feature.
5
5
  Invoked directly via /fledge-brief, not auto-triggered.
6
6
  metadata:
7
7
  type: workflow
8
- allowed-tools: Bash(node scripts/brief.js *)
8
+ allowed-tools: Bash(node *scripts/brief.js*)
9
9
  ---
10
10
 
11
+ ## Lifecycle overview
12
+
13
+ The brief skill owns two workflow phases: **Brief** (capture what to build) and **Enrich** (connect to project knowledge). See [docs/workflow.md](../../docs/workflow.md) for the full workflow.
14
+
15
+ ```
16
+ Brief → Enrich → [Implement] → [Verify] → [Complete]
17
+ └── this skill ──┘ └── implement skill (future) ──────────┘
18
+ ```
19
+
20
+ **States this skill manages:** `draft → ready`
21
+ **States managed by the implement skill:** `ready → active → completed`
22
+
23
+ A brief directory contains three files:
24
+
25
+ ```
26
+ .fledge/briefs/<name>/
27
+ brief.md what, why, scope, design decisions (product-level)
28
+ spec.md data models, APIs, external services (technical context)
29
+ tasks.md implementation tasks (empty until implement phase)
30
+ ```
31
+
11
32
  ## Available scripts
12
33
 
13
34
  **Important:** Run all scripts with `node` (e.g. `node scripts/brief.js list`). Always pass `--project-dir` pointing to the project root.
14
35
 
15
36
  - **`node scripts/brief.js create <name> --project-dir <path>`** -- Create a new brief with stub files
16
- - **`node scripts/brief.js start <name> --project-dir <path>`** -- Transition a brief from draft to active
37
+ - **`node scripts/brief.js ready <name> --project-dir <path>`** -- Transition a brief from draft to ready
38
+ - **`node scripts/brief.js start <name> --project-dir <path>`** -- Transition a brief from ready to active
17
39
  - **`node scripts/brief.js complete <name> --project-dir <path>`** -- Transition a brief from active to completed
40
+ - **`node scripts/brief.js cancel <name> --project-dir <path>`** -- Cancel a brief
18
41
  - **`node scripts/brief.js status <name> --project-dir <path>`** -- Show status and task progress
19
42
  - **`node scripts/brief.js list [--status <status>] --project-dir <path>`** -- List all briefs with progress and summary
20
43
  - **`node scripts/brief.js validate <name> --project-dir <path>`** -- Validate brief files against schemas
@@ -25,8 +48,8 @@ allowed-tools: Bash(node scripts/brief.js *)
25
48
  Ask what the user wants to do, or infer from context. Present these options:
26
49
 
27
50
  1. **New brief** -- plan a new feature from scratch. Proceed to Step 1.
28
- 2. **Continue a brief** -- pick up an existing brief. Proceed to Step 4.
29
- 3. **Complete a brief** -- wrap up a finished feature. Proceed to Step 5.
51
+ 2. **Continue a brief** -- pick up an existing brief. Proceed to Step 5.
52
+ 3. **Complete a brief** -- wrap up a finished feature. Proceed to Step 6.
30
53
 
31
54
  If unclear, run `node scripts/brief.js list --project-dir <path>` to show current briefs and ask.
32
55
 
@@ -66,59 +89,58 @@ Proceed to Step 3.
66
89
 
67
90
  ---
68
91
 
69
- ## Step 3: Break down into tasks
92
+ ## Step 3: Enrich with project knowledge
70
93
 
71
- Define the implementation tasks in `tasks.md`. Each task should be:
94
+ Connect the brief to the technical reality of the project. Read the codebase and project knowledge sources to understand:
72
95
 
73
- - **Small enough** to be a single focused unit of work
74
- - **Specific enough** that someone (or an agent) can start without further clarification
75
- - **Grouped** by concern when the feature spans multiple areas (e.g. backend, frontend, data)
96
+ - **Data models**: what existing models does the feature interact with? What new models are needed?
97
+ - **APIs**: what endpoints exist? What new endpoints are needed?
98
+ - **External services**: what third-party integrations are involved?
99
+ - **Domain concepts**: what domain terms and relationships are relevant?
76
100
 
77
- Write the tasks into the `tasks.md` frontmatter:
101
+ Write this technical context into `spec.md`. The spec grounds the product-level brief in the project's actual structure. It does **not** include implementation tasks or technology-specific guidance (those come from the implement skill and technology skills).
102
+
103
+ Proceed to Step 4.
78
104
 
79
- ```yaml
80
- ---
81
- tasks:
82
- - name: <clear, actionable task name>
83
- group: <area>
84
- done: false
85
105
  ---
86
- ```
87
106
 
88
- Order tasks by dependency: tasks that others depend on come first within their group.
107
+ ## Step 4: Mark as ready
108
+
109
+ Run `node scripts/brief.js validate <name> --project-dir <path>` to confirm the brief is valid.
89
110
 
90
- After writing tasks, run `node scripts/brief.js validate <name> --project-dir <path>` to confirm the brief is valid, then run `node scripts/brief.js start <name> --project-dir <path>` to transition to active.
111
+ Present the complete brief (`brief.md`) and spec (`spec.md`) to the user for review.
91
112
 
92
- Present the complete brief and task list to the user for review before starting.
113
+ Once approved, run `node scripts/brief.js ready <name> --project-dir <path>` to transition to ready. The brief is now ready for the implement skill to pick up.
93
114
 
94
115
  ---
95
116
 
96
- ## Step 4: Continue a brief
117
+ ## Step 5: Continue a brief
97
118
 
98
119
  Run `node scripts/brief.js list --project-dir <path>` to show all briefs. If the user does not specify which brief, ask them to pick one.
99
120
 
100
- Run `node scripts/brief.js status <name> --project-dir <path>` to show progress. Read the brief and tasks files to understand the full context.
121
+ Run `node scripts/brief.js status <name> --project-dir <path>` to show progress. Read the brief, spec, and tasks files to understand the full context.
101
122
 
102
123
  From here, the user may want to:
103
- - **Discuss a task** -- talk through approach before implementing
104
- - **Update tasks** -- mark tasks as done, add new tasks, reorder
105
- - **Revise the brief** -- update scope or design decisions based on what was learned during implementation
124
+ - **Discuss the brief** -- talk through scope or design decisions
125
+ - **Update the spec** -- add or revise technical context
126
+ - **Revise the brief** -- update scope or design decisions based on what was learned
127
+ - **Send back to draft** -- if a ready brief needs significant rework, update the status back to draft
106
128
 
107
- When updating task status, modify the `tasks.md` frontmatter directly, then run `node scripts/brief.js status <name> --project-dir <path>` to confirm the update.
129
+ When updating files, run `node scripts/brief.js validate <name> --project-dir <path>` afterward to confirm validity.
108
130
 
109
131
  ---
110
132
 
111
- ## Step 5: Complete a brief
133
+ ## Step 6: Complete a brief
112
134
 
113
135
  Run `node scripts/brief.js status <name> --project-dir <path>` to verify all tasks are done.
114
136
 
115
137
  If there are incomplete tasks, ask the user whether to:
116
- 1. Mark remaining tasks as done (if they were completed outside this conversation)
117
- 2. Remove tasks that are no longer needed
138
+ 1. Mark remaining tasks as completed (if they were completed outside this conversation)
139
+ 2. Mark tasks as skipped (if they are no longer needed)
118
140
  3. Continue working on them first
119
141
 
120
142
  Write a summary into the `brief.md` frontmatter `summary` field. The summary should be one to two sentences capturing:
121
143
  - What was built
122
144
  - Key decisions or patterns established that future features should know about
123
145
 
124
- Run `node 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.
146
+ Run `node scripts/brief.js complete <name> --project-dir <path>` to transition to completed. The script validates that all tasks are done or skipped and the summary is present.