@kernloop/faculty-scrum 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Zujkowski and Kernloop contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Program goal decomposition — the MECHANICAL half, ONE ALTITUDE UP from the
3
+ * PM's plan decomposition (spec §5.4; CLM-0096). Where faculty-workforce's
4
+ * `decomposePlan` splits a ratified plan into agent-assigned children, this
5
+ * splits a program GOAL into an epic/story TaskContract tree, tagging each
6
+ * child with its program altitude/track/sprint as constraint tags.
7
+ *
8
+ * No model is ever called here: a PM model WRITES the story specs upstream;
9
+ * this function takes them as plain input and enforces the contract — schema
10
+ * validity, id/parent/overlay derivation, ceiling clamping, the budget-sum
11
+ * invariant, and well-formed constraint tags. The invariant is therefore
12
+ * unit-testable without any model in the loop. This faculty imports only
13
+ * @kernloop/contracts and zod (constitutional rule 5).
14
+ */
15
+ import { type TaskContract } from '@kernloop/contracts';
16
+ import { z } from 'zod';
17
+ /**
18
+ * One story (or sub-epic) as proposed by the PM. Identity, overlay, and
19
+ * authority are NOT inputs — they are derived from the parent here, so a PM
20
+ * cannot grant what the parent does not hold. `altitude` is the child's
21
+ * program rung (e.g. 'epic' or 'story'); optional `track`/`sprint` group it.
22
+ */
23
+ export declare const StorySpecSchema: z.ZodObject<{
24
+ goal: z.ZodString;
25
+ constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
26
+ budget: z.ZodObject<{
27
+ tokens: z.ZodNumber;
28
+ usd: z.ZodNumber;
29
+ wallClockMin: z.ZodNumber;
30
+ }, z.core.$strict>;
31
+ evidence: z.ZodOptional<z.ZodArray<z.ZodObject<{
32
+ kind: z.ZodEnum<{
33
+ test: "test";
34
+ ci: "ci";
35
+ doc: "doc";
36
+ eval: "eval";
37
+ }>;
38
+ ref: z.ZodString;
39
+ }, z.core.$strict>>>;
40
+ definitionOfDone: z.ZodOptional<z.ZodArray<z.ZodObject<{
41
+ name: z.ZodString;
42
+ command: z.ZodString;
43
+ }, z.core.$strict>>>;
44
+ assignTo: z.ZodEnum<{
45
+ pm: "pm";
46
+ coder: "coder";
47
+ reviewer: "reviewer";
48
+ documenter: "documenter";
49
+ researcher: "researcher";
50
+ }>;
51
+ altitude: z.ZodEnum<{
52
+ epic: "epic";
53
+ story: "story";
54
+ task: "task";
55
+ }>;
56
+ track: z.ZodOptional<z.ZodString>;
57
+ sprint: z.ZodOptional<z.ZodString>;
58
+ }, z.core.$strict>;
59
+ export type StorySpec = z.infer<typeof StorySpecSchema>;
60
+ /** Input to {@link decomposeGoal}. */
61
+ export interface DecomposeGoalInput {
62
+ parent: TaskContract;
63
+ subtasks: StorySpec[];
64
+ }
65
+ /**
66
+ * Derive zod-valid child TaskContracts from a program parent plus PM-proposed
67
+ * story specs, ONE ALTITUDE UP from `decomposePlan` (CLM-0096). Enforces:
68
+ *
69
+ * - ALTITUDE DESCENT: when the parent carries an `altitude`, it decomposes
70
+ * exactly one rung down (epic→story, story→task) and a `task` parent is a
71
+ * leaf that cannot decompose — a violation throws {@link AltitudeDescentError}.
72
+ * A parent with no altitude (the program root) is the unconstrained entry.
73
+ * - BUDGET INVARIANT: child budgets sum within the parent's on each of tokens,
74
+ * usd, and wallClockMin independently; any breach throws
75
+ * {@link ScrumBudgetExceededError} naming the dimension and amounts. An exact
76
+ * sum is within budget. Zero/negative child slices are rejected.
77
+ * - Identity: `child.id = parent.id + '.<n>'` (1-based input order),
78
+ * `child.parent = parent.id`, `child.overlay = parent.overlay`.
79
+ * - Authority: `child.authorityCeiling = min(parent ceiling, 'suggest')` — a PM
80
+ * cannot grant authority the parent does not hold, and generative program
81
+ * work enters at `suggest` (spec §3.2).
82
+ * - Constraints: parent constraints bind children, then the story's own, then
83
+ * the program tags `altitude:<v>` (+ optional `track:`/`sprint:`), then the
84
+ * routing tag `assign:agent.<template>`. Emitted tags are validated via
85
+ * {@link parseConstraintTags} so an unsafe value cannot escape.
86
+ */
87
+ export declare function decomposeGoal(input: DecomposeGoalInput): TaskContract[];
88
+ //# sourceMappingURL=decompose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decompose.d.ts","sourceRoot":"","sources":["../src/decompose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAOL,KAAK,YAAY,EAElB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA2CxB;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAc1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,sCAAsC;AACtC,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB;AAsFD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAmBvE"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Program goal decomposition — the MECHANICAL half, ONE ALTITUDE UP from the
3
+ * PM's plan decomposition (spec §5.4; CLM-0096). Where faculty-workforce's
4
+ * `decomposePlan` splits a ratified plan into agent-assigned children, this
5
+ * splits a program GOAL into an epic/story TaskContract tree, tagging each
6
+ * child with its program altitude/track/sprint as constraint tags.
7
+ *
8
+ * No model is ever called here: a PM model WRITES the story specs upstream;
9
+ * this function takes them as plain input and enforces the contract — schema
10
+ * validity, id/parent/overlay derivation, ceiling clamping, the budget-sum
11
+ * invariant, and well-formed constraint tags. The invariant is therefore
12
+ * unit-testable without any model in the loop. This faculty imports only
13
+ * @kernloop/contracts and zod (constitutional rule 5).
14
+ */
15
+ import { AltitudeSchema, TaskContractSchema, TierSchema, constraintTag, parseConstraintTags, } from '@kernloop/contracts';
16
+ import { z } from 'zod';
17
+ import { AltitudeDescentError, InvalidParentError, InvalidStorySpecError, ScrumBudgetExceededError, } from './errors.js';
18
+ /** The altitude exactly one rung below a decomposable parent (the descent
19
+ * ladder epic → story → task). A `task` is a leaf (no rung below). */
20
+ const CHILD_ALTITUDE = {
21
+ epic: 'story',
22
+ story: 'task',
23
+ task: undefined,
24
+ };
25
+ /** Authority ladder, lowest first (spec §3.2). */
26
+ const TIER_ORDER = TierSchema.options;
27
+ /** The lower of two tiers on the ladder. */
28
+ function minTier(a, b) {
29
+ return TIER_ORDER.indexOf(a) <= TIER_ORDER.indexOf(b) ? a : b;
30
+ }
31
+ /**
32
+ * The shipped agent-template names a story may be assigned to. Inlined rather
33
+ * than imported from @kernloop/faculty-workforce — a faculty→faculty import is
34
+ * forbidden (constitutional rule 5). Mirrors workforce's `SHIPPED_TEMPLATE_NAMES`;
35
+ * custom templates are a later increment (deferred: #177).
36
+ */
37
+ const STORY_ASSIGNEES = ['pm', 'coder', 'reviewer', 'documenter', 'researcher'];
38
+ /**
39
+ * A child budget as proposed. Stricter than TaskContract's nonnegative budget:
40
+ * a zero or negative slice on any dimension is rejected — a child with nothing
41
+ * to spend is a stub, and stubs are forbidden.
42
+ */
43
+ const StoryBudgetSchema = z.strictObject({
44
+ tokens: z.number().int().positive(),
45
+ usd: z.number().positive(),
46
+ wallClockMin: z.number().positive(),
47
+ });
48
+ /**
49
+ * One story (or sub-epic) as proposed by the PM. Identity, overlay, and
50
+ * authority are NOT inputs — they are derived from the parent here, so a PM
51
+ * cannot grant what the parent does not hold. `altitude` is the child's
52
+ * program rung (e.g. 'epic' or 'story'); optional `track`/`sprint` group it.
53
+ */
54
+ export const StorySpecSchema = z.strictObject({
55
+ goal: z.string().min(1),
56
+ constraints: z.array(z.string().min(1)).optional(),
57
+ budget: StoryBudgetSchema,
58
+ evidence: TaskContractSchema.shape.evidence.optional(),
59
+ definitionOfDone: TaskContractSchema.shape.definitionOfDone.optional(),
60
+ /** Shipped template name this child is assigned to (custom templates: later). */
61
+ assignTo: z.enum(STORY_ASSIGNEES),
62
+ /** The child's program altitude — epic | story | task. */
63
+ altitude: AltitudeSchema,
64
+ /** Optional track grouping (safe label charset, enforced on emit). */
65
+ track: z.string().optional(),
66
+ /** Optional sprint grouping (safe label charset, enforced on emit). */
67
+ sprint: z.string().optional(),
68
+ });
69
+ /** Parse one story spec, wrapping any schema failure as a typed error. */
70
+ function parseStory(spec, index) {
71
+ const parsed = StorySpecSchema.safeParse(spec);
72
+ if (!parsed.success) {
73
+ throw new InvalidStorySpecError(index, z.prettifyError(parsed.error));
74
+ }
75
+ return parsed.data;
76
+ }
77
+ /**
78
+ * Enforce altitude descent (CLM-0096): when the parent carries an `altitude`
79
+ * tag, it must decompose exactly one rung down — `epic`→`story`, `story`→`task`
80
+ * — and a `task` parent is a leaf that cannot be decomposed at all. A parent
81
+ * with NO altitude is the program root (the unconstrained entry) and is not
82
+ * checked, so the model still chooses the entry granularity. A child whose
83
+ * altitude is not exactly the expected rung throws {@link AltitudeDescentError}.
84
+ */
85
+ function checkAltitudeDescent(parentAltitude, stories) {
86
+ if (parentAltitude === undefined)
87
+ return; // root entry — unconstrained
88
+ const expected = CHILD_ALTITUDE[parentAltitude];
89
+ if (expected === undefined) {
90
+ throw new AltitudeDescentError(parentAltitude, -1); // a task is a leaf
91
+ }
92
+ stories.forEach((spec, index) => {
93
+ if (spec.altitude !== expected) {
94
+ throw new AltitudeDescentError(parentAltitude, index, expected, spec.altitude);
95
+ }
96
+ });
97
+ }
98
+ /** Enforce sum(child) ≤ parent independently per budget dimension. */
99
+ function checkBudgetInvariant(parent, stories) {
100
+ for (const dimension of ['tokens', 'usd', 'wallClockMin']) {
101
+ const childSum = stories.reduce((sum, s) => sum + s.budget[dimension], 0);
102
+ if (childSum > parent.budget[dimension]) {
103
+ throw new ScrumBudgetExceededError(dimension, parent.budget[dimension], childSum);
104
+ }
105
+ }
106
+ }
107
+ /** Build a child's constraint array: the parent's FREE-FORM constraints
108
+ * (`inherited`, its program tags stripped) + the story's own + the child's
109
+ * freshly-derived altitude/track/sprint + assignment. The parent's own program
110
+ * tags are NOT inherited — re-deriving them per child is what lets an
111
+ * altitude-bearing parent decompose without a duplicate `altitude:` tag. */
112
+ function childConstraints(inherited, spec) {
113
+ return [
114
+ ...inherited,
115
+ ...(spec.constraints ?? []),
116
+ constraintTag('altitude', spec.altitude),
117
+ ...(spec.track !== undefined ? [constraintTag('track', spec.track)] : []),
118
+ ...(spec.sprint !== undefined ? [constraintTag('sprint', spec.sprint)] : []),
119
+ constraintTag('assign', `agent.${spec.assignTo}`),
120
+ ];
121
+ }
122
+ /** Map one validated story spec to a zod-valid child TaskContract. */
123
+ function toChild(parent, inherited, spec, index) {
124
+ const constraints = childConstraints(inherited, spec);
125
+ // Defense in depth: the emitted program tags must read back cleanly — an
126
+ // unsafe track/sprint or bad altitude surfaces here as a typed story error.
127
+ try {
128
+ parseConstraintTags(constraints);
129
+ }
130
+ catch (error) {
131
+ throw new InvalidStorySpecError(index, error instanceof Error ? error.message : String(error));
132
+ }
133
+ return TaskContractSchema.parse({
134
+ id: `${parent.id}.${index + 1}`,
135
+ parent: parent.id,
136
+ goal: spec.goal,
137
+ constraints,
138
+ budget: spec.budget,
139
+ evidence: spec.evidence ?? [],
140
+ definitionOfDone: spec.definitionOfDone ?? [],
141
+ authorityCeiling: minTier(parent.authorityCeiling, 'suggest'),
142
+ overlay: parent.overlay,
143
+ });
144
+ }
145
+ /**
146
+ * Derive zod-valid child TaskContracts from a program parent plus PM-proposed
147
+ * story specs, ONE ALTITUDE UP from `decomposePlan` (CLM-0096). Enforces:
148
+ *
149
+ * - ALTITUDE DESCENT: when the parent carries an `altitude`, it decomposes
150
+ * exactly one rung down (epic→story, story→task) and a `task` parent is a
151
+ * leaf that cannot decompose — a violation throws {@link AltitudeDescentError}.
152
+ * A parent with no altitude (the program root) is the unconstrained entry.
153
+ * - BUDGET INVARIANT: child budgets sum within the parent's on each of tokens,
154
+ * usd, and wallClockMin independently; any breach throws
155
+ * {@link ScrumBudgetExceededError} naming the dimension and amounts. An exact
156
+ * sum is within budget. Zero/negative child slices are rejected.
157
+ * - Identity: `child.id = parent.id + '.<n>'` (1-based input order),
158
+ * `child.parent = parent.id`, `child.overlay = parent.overlay`.
159
+ * - Authority: `child.authorityCeiling = min(parent ceiling, 'suggest')` — a PM
160
+ * cannot grant authority the parent does not hold, and generative program
161
+ * work enters at `suggest` (spec §3.2).
162
+ * - Constraints: parent constraints bind children, then the story's own, then
163
+ * the program tags `altitude:<v>` (+ optional `track:`/`sprint:`), then the
164
+ * routing tag `assign:agent.<template>`. Emitted tags are validated via
165
+ * {@link parseConstraintTags} so an unsafe value cannot escape.
166
+ */
167
+ export function decomposeGoal(input) {
168
+ const parentResult = TaskContractSchema.safeParse(input.parent);
169
+ if (!parentResult.success) {
170
+ throw new InvalidParentError(z.prettifyError(parentResult.error));
171
+ }
172
+ const parent = parentResult.data;
173
+ // Parse the parent's program tags ONCE: its altitude drives descent, and its
174
+ // free-form (non-tag) constraints are what children inherit. A malformed
175
+ // parent tag is a typed InvalidParentError, never a raw throw.
176
+ let parentTags;
177
+ try {
178
+ parentTags = parseConstraintTags(parent.constraints);
179
+ }
180
+ catch (error) {
181
+ throw new InvalidParentError(error instanceof Error ? error.message : String(error));
182
+ }
183
+ const stories = input.subtasks.map((spec, i) => parseStory(spec, i));
184
+ checkAltitudeDescent(parentTags.altitude, stories);
185
+ checkBudgetInvariant(parent, stories);
186
+ return stories.map((spec, i) => toChild(parent, parentTags.other, spec, i));
187
+ }
188
+ //# sourceMappingURL=decompose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decompose.js","sourceRoot":"","sources":["../src/decompose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Typed errors thrown at the scrum faculty's boundaries. Callers discriminate
3
+ * on `name` or `instanceof` — never on message text. Mirrors the shapes of
4
+ * faculty-workforce's decomposition errors (spec §5.4).
5
+ */
6
+ import type { Altitude, TaskContract } from '@kernloop/contracts';
7
+ /** The three independently-summed budget dimensions of a TaskContract. */
8
+ export type BudgetDimension = keyof TaskContract['budget'];
9
+ /**
10
+ * Thrown when the sum of child (story) budgets exceeds the parent budget on
11
+ * any dimension (spec §5.4: child TaskContract budgets "must sum within the
12
+ * parent's"; CLM-0096). Carries the offending dimension and both amounts so a
13
+ * caller can report — or replan — without parsing text.
14
+ */
15
+ export declare class ScrumBudgetExceededError extends Error {
16
+ readonly dimension: BudgetDimension;
17
+ readonly parentAmount: number;
18
+ readonly childSum: number;
19
+ constructor(dimension: BudgetDimension, parentAmount: number, childSum: number);
20
+ }
21
+ /**
22
+ * Thrown when the parent contract handed to decomposeGoal fails
23
+ * TaskContractSchema validation (charter: zod-validate at every contract
24
+ * boundary).
25
+ */
26
+ export declare class InvalidParentError extends Error {
27
+ constructor(detail: string);
28
+ }
29
+ /**
30
+ * Thrown when a story spec is malformed for a reason other than the budget-sum
31
+ * invariant: schema violation, zero/negative budget dimension, an `assignTo`
32
+ * naming no shipped template, or an `altitude` outside the enum.
33
+ */
34
+ export declare class InvalidStorySpecError extends Error {
35
+ /** Zero-based index of the offending story in the input array. */
36
+ readonly index: number;
37
+ constructor(index: number, detail: string);
38
+ }
39
+ /**
40
+ * Thrown when a decomposition violates altitude descent (spec §5.4; CLM-0096):
41
+ * an altitude-bearing parent must decompose exactly ONE rung down (epic→story,
42
+ * story→task), and a `task`-altitude parent is a LEAF that cannot decompose at
43
+ * all. (A parent with no altitude — the program root — is the unconstrained
44
+ * entry and is not checked.) Carries the parent + expected/actual child
45
+ * altitudes so a caller can report or replan without parsing text. For a
46
+ * task-parent leaf violation, `expected`/`actual` are omitted.
47
+ */
48
+ export declare class AltitudeDescentError extends Error {
49
+ readonly parentAltitude: Altitude;
50
+ readonly expected?: Altitude;
51
+ readonly actual?: Altitude;
52
+ /** Zero-based index of the offending child, or -1 for a task-leaf parent. */
53
+ readonly index: number;
54
+ constructor(parentAltitude: Altitude, index: number, expected?: Altitude, actual?: Altitude);
55
+ }
56
+ /**
57
+ * Thrown by {@link programLabels} when an emitted label would not satisfy the
58
+ * tracker's label charset. Unreachable from valid decomposed input (altitude is
59
+ * an enum; track/sprint/assign are charset-bound upstream) — it is a typed
60
+ * invariant guard so a FUTURE label source that slips an unsafe value surfaces
61
+ * as a clean, discriminable error rather than escaping as a raw throw.
62
+ */
63
+ export declare class UnsafeLabelError extends Error {
64
+ constructor(label: string);
65
+ }
66
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAElE,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;AAE3D;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAU/E;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,MAAM,EAAE,MAAM;CAI3B;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK1C;AAED;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,QAAQ;CAa5F;AAED;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,KAAK,EAAE,MAAM;CAI1B"}
package/dist/errors.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Thrown when the sum of child (story) budgets exceeds the parent budget on
3
+ * any dimension (spec §5.4: child TaskContract budgets "must sum within the
4
+ * parent's"; CLM-0096). Carries the offending dimension and both amounts so a
5
+ * caller can report — or replan — without parsing text.
6
+ */
7
+ export class ScrumBudgetExceededError extends Error {
8
+ dimension;
9
+ parentAmount;
10
+ childSum;
11
+ constructor(dimension, parentAmount, childSum) {
12
+ super(`child budgets exceed parent on ${dimension}: ` +
13
+ `sum ${childSum} > parent ${parentAmount} (spec §5.4)`);
14
+ this.name = 'ScrumBudgetExceededError';
15
+ this.dimension = dimension;
16
+ this.parentAmount = parentAmount;
17
+ this.childSum = childSum;
18
+ }
19
+ }
20
+ /**
21
+ * Thrown when the parent contract handed to decomposeGoal fails
22
+ * TaskContractSchema validation (charter: zod-validate at every contract
23
+ * boundary).
24
+ */
25
+ export class InvalidParentError extends Error {
26
+ constructor(detail) {
27
+ super(detail);
28
+ this.name = 'InvalidParentError';
29
+ }
30
+ }
31
+ /**
32
+ * Thrown when a story spec is malformed for a reason other than the budget-sum
33
+ * invariant: schema violation, zero/negative budget dimension, an `assignTo`
34
+ * naming no shipped template, or an `altitude` outside the enum.
35
+ */
36
+ export class InvalidStorySpecError extends Error {
37
+ /** Zero-based index of the offending story in the input array. */
38
+ index;
39
+ constructor(index, detail) {
40
+ super(`story[${index}]: ${detail}`);
41
+ this.name = 'InvalidStorySpecError';
42
+ this.index = index;
43
+ }
44
+ }
45
+ /**
46
+ * Thrown when a decomposition violates altitude descent (spec §5.4; CLM-0096):
47
+ * an altitude-bearing parent must decompose exactly ONE rung down (epic→story,
48
+ * story→task), and a `task`-altitude parent is a LEAF that cannot decompose at
49
+ * all. (A parent with no altitude — the program root — is the unconstrained
50
+ * entry and is not checked.) Carries the parent + expected/actual child
51
+ * altitudes so a caller can report or replan without parsing text. For a
52
+ * task-parent leaf violation, `expected`/`actual` are omitted.
53
+ */
54
+ export class AltitudeDescentError extends Error {
55
+ parentAltitude;
56
+ expected;
57
+ actual;
58
+ /** Zero-based index of the offending child, or -1 for a task-leaf parent. */
59
+ index;
60
+ constructor(parentAltitude, index, expected, actual) {
61
+ super(expected === undefined
62
+ ? `a "${parentAltitude}" is a leaf and cannot be decomposed (spec §5.4)`
63
+ : `story[${index}]: altitude "${actual ?? '<missing>'}" violates descent — ` +
64
+ `a "${parentAltitude}" parent decomposes to "${expected}" children (spec §5.4)`);
65
+ this.name = 'AltitudeDescentError';
66
+ this.parentAltitude = parentAltitude;
67
+ this.index = index;
68
+ if (expected !== undefined)
69
+ this.expected = expected;
70
+ if (actual !== undefined)
71
+ this.actual = actual;
72
+ }
73
+ }
74
+ /**
75
+ * Thrown by {@link programLabels} when an emitted label would not satisfy the
76
+ * tracker's label charset. Unreachable from valid decomposed input (altitude is
77
+ * an enum; track/sprint/assign are charset-bound upstream) — it is a typed
78
+ * invariant guard so a FUTURE label source that slips an unsafe value surfaces
79
+ * as a clean, discriminable error rather than escaping as a raw throw.
80
+ */
81
+ export class UnsafeLabelError extends Error {
82
+ constructor(label) {
83
+ super(`program label "${label}" is not tracker-label-safe`);
84
+ this.name = 'UnsafeLabelError';
85
+ }
86
+ }
87
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACxC,SAAS,CAAkB;IAC3B,YAAY,CAAS;IACrB,QAAQ,CAAS;IAE1B,YAAY,SAA0B,EAAE,YAAoB,EAAE,QAAgB;QAC5E,KAAK,CACH,kCAAkC,SAAS,IAAI;YAC7C,OAAO,QAAQ,aAAa,YAAY,cAAc,CACzD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,kEAAkE;IACzD,KAAK,CAAS;IAEvB,YAAY,KAAa,EAAE,MAAc;QACvC,KAAK,CAAC,SAAS,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,cAAc,CAAW;IACzB,QAAQ,CAAY;IACpB,MAAM,CAAY;IAC3B,6EAA6E;IACpE,KAAK,CAAS;IAEvB,YAAY,cAAwB,EAAE,KAAa,EAAE,QAAmB,EAAE,MAAiB;QACzF,KAAK,CACH,QAAQ,KAAK,SAAS;YACpB,CAAC,CAAC,MAAM,cAAc,kDAAkD;YACxE,CAAC,CAAC,SAAS,KAAK,gBAAgB,MAAM,IAAI,WAAW,uBAAuB;gBACxE,MAAM,cAAc,2BAA2B,QAAQ,wBAAwB,CACtF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACrD,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACjD,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,KAAa;QACvB,KAAK,CAAC,kBAAkB,KAAK,6BAA6B,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @kernloop/faculty-scrum — the scrum/program-decomposition faculty (spec §5.4).
3
+ *
4
+ * Decomposes a program goal into an epic/story TaskContract tree ONE ALTITUDE
5
+ * UP from the PM's plan decomposition: child budgets must sum within the
6
+ * parent's on every dimension (the budget-sum invariant), and each child
7
+ * carries its program altitude/track/sprint as constraint tags (CLM-0096).
8
+ * GitHub-free and model-free — generative work happens elsewhere; this package
9
+ * is pure mechanical enforcement, surfaced through the suggest-tier
10
+ * `kernloop program decompose` CLI. It imports only @kernloop/contracts and
11
+ * external dependencies (constitutional rule 5).
12
+ */
13
+ export { decomposeGoal, StorySpecSchema } from './decompose.js';
14
+ export type { StorySpec, DecomposeGoalInput } from './decompose.js';
15
+ export { programLabels, programIssueBody } from './labels.js';
16
+ export { AltitudeDescentError, InvalidParentError, InvalidStorySpecError, ScrumBudgetExceededError, UnsafeLabelError, } from './errors.js';
17
+ export type { BudgetDimension } from './errors.js';
18
+ export { scrumManifest } from './manifest.js';
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAChE,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @kernloop/faculty-scrum — the scrum/program-decomposition faculty (spec §5.4).
3
+ *
4
+ * Decomposes a program goal into an epic/story TaskContract tree ONE ALTITUDE
5
+ * UP from the PM's plan decomposition: child budgets must sum within the
6
+ * parent's on every dimension (the budget-sum invariant), and each child
7
+ * carries its program altitude/track/sprint as constraint tags (CLM-0096).
8
+ * GitHub-free and model-free — generative work happens elsewhere; this package
9
+ * is pure mechanical enforcement, surfaced through the suggest-tier
10
+ * `kernloop program decompose` CLI. It imports only @kernloop/contracts and
11
+ * external dependencies (constitutional rule 5).
12
+ */
13
+ export { decomposeGoal, StorySpecSchema } from './decompose.js';
14
+ export { programLabels, programIssueBody } from './labels.js';
15
+ export { AltitudeDescentError, InvalidParentError, InvalidStorySpecError, ScrumBudgetExceededError, UnsafeLabelError, } from './errors.js';
16
+ export { scrumManifest } from './manifest.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The constraint-tag → GitHub-label map + issue-body rendering for program
3
+ * emission (spec §5.4; CLM-0097). The half of `kernloop program emit` that is
4
+ * PURE and GitHub-free: it turns a decomposed child TaskContract into the
5
+ * label set and issue body the CLI will hand to the hardened @kernloop/tracker.
6
+ *
7
+ * Faculty isolation (constitutional rule 5): this module imports ONLY
8
+ * @kernloop/contracts and zod — never the tracker and never the CLI. The label
9
+ * charset it must satisfy (the tracker's `LabelSchema`) is therefore asserted
10
+ * here with an inlined copy of the same regex, not by importing the tracker.
11
+ */
12
+ import { type TaskContract } from '@kernloop/contracts';
13
+ /**
14
+ * Map a TaskContract's constraint tags to GitHub labels through the one
15
+ * `LABEL_MAP` table — so the GitHub view and future loop routing never
16
+ * diverge [CLM-0097]. Emits `altitude:<a>`, `track:<t>`, `sprint:<s>`, and
17
+ * `agent:<t>` (from `assign:agent.<t>`); free-form/unknown constraints (the
18
+ * `other` bucket) produce NO label. Output is deduped and every label is
19
+ * asserted tracker-label-safe ({@link assertLabelSafe}) before return.
20
+ */
21
+ export declare function programLabels(constraints: readonly string[]): string[];
22
+ /**
23
+ * Render the GitHub issue body for a decomposed child node (CLM-0097): the
24
+ * node goal as prose, then the replayable task-shaped payload (id, parent,
25
+ * goal, constraints, budget) as a fenced JSON block a human or `kernloop run`
26
+ * can pick up. Pure — no secrets, no tracker, no model. Mirrors the spirit of
27
+ * faculty-observer's `issueBody`.
28
+ */
29
+ export declare function programIssueBody(node: TaskContract): string;
30
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAwD7E;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAatE;AAeD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAU3D"}
package/dist/labels.js ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The constraint-tag → GitHub-label map + issue-body rendering for program
3
+ * emission (spec §5.4; CLM-0097). The half of `kernloop program emit` that is
4
+ * PURE and GitHub-free: it turns a decomposed child TaskContract into the
5
+ * label set and issue body the CLI will hand to the hardened @kernloop/tracker.
6
+ *
7
+ * Faculty isolation (constitutional rule 5): this module imports ONLY
8
+ * @kernloop/contracts and zod — never the tracker and never the CLI. The label
9
+ * charset it must satisfy (the tracker's `LabelSchema`) is therefore asserted
10
+ * here with an inlined copy of the same regex, not by importing the tracker.
11
+ */
12
+ import { parseConstraintTags } from '@kernloop/contracts';
13
+ import { z } from 'zod';
14
+ import { UnsafeLabelError } from './errors.js';
15
+ /**
16
+ * The tracker `LabelSchema` charset, INLINED (faculty-scrum must not import the
17
+ * tracker — constitutional rule 5). Kept byte-identical to
18
+ * `@kernloop/tracker`'s `LabelSchema` regex (leading alphanumeric, then the
19
+ * `[A-Za-z0-9 _.\-/:]` charset, ≤80, no leading `-`) so a label this module
20
+ * emits is provably accepted at the sink. {@link assertLabelSafe} applies it.
21
+ */
22
+ const LABEL_SAFE = /^[A-Za-z0-9][A-Za-z0-9 _.\-/:]*$/;
23
+ /**
24
+ * Assert an emitted label satisfies the tracker's label charset (≤80, leading
25
+ * alphanumeric). Every value `programLabels` emits is already charset-bound
26
+ * upstream — altitude is the `epic|story|task` enum; track/sprint/assign are
27
+ * validated by `parseConstraintTags` against `[A-Za-z0-9._-]` — so this is
28
+ * defense in depth: a future tag source that slips an unsafe value cannot
29
+ * escape to `gh` as a flag-shaped label.
30
+ */
31
+ function assertLabelSafe(label) {
32
+ if (label.length > 80 || !LABEL_SAFE.test(label)) {
33
+ throw new UnsafeLabelError(label);
34
+ }
35
+ }
36
+ /**
37
+ * THE ONE MAP: how a single program constraint tag becomes a GitHub label.
38
+ * Keyed by the {@link parseConstraintTags} field, each entry renders the
39
+ * typed value to a label string. This is the single source of truth so the
40
+ * GitHub view and future loop routing can never diverge on the spelling.
41
+ *
42
+ * Note the `assign` transform: `assign:agent.<t>` (the constraint carrier) maps
43
+ * to the label `agent:<t>` — the key changes from `assign` to `agent` and the
44
+ * `agent.<t>` value's `.` becomes `:`. The other three keys pass through.
45
+ *
46
+ * INCREMENT 4 (#52) NOTE: the REVERSE direction (label → tag, for loop
47
+ * routing) is a trivial later addition over this same table, and when the
48
+ * kernel/router needs it the table moves to a shared home. For now
49
+ * faculty-scrum is the only consumer.
50
+ */
51
+ const LABEL_MAP = {
52
+ altitude: (v) => `altitude:${v}`,
53
+ track: (v) => `track:${v}`,
54
+ sprint: (v) => `sprint:${v}`,
55
+ // `assign:agent.<t>` → `agent:<t>` (drop the `assign:` prefix, `.` → `:`).
56
+ // Anything not of the `agent.<t>` shape produces no label (skipped).
57
+ assign: (v) => (v.startsWith('agent.') ? `agent:${v.slice('agent.'.length)}` : undefined),
58
+ };
59
+ /**
60
+ * Map a TaskContract's constraint tags to GitHub labels through the one
61
+ * `LABEL_MAP` table — so the GitHub view and future loop routing never
62
+ * diverge [CLM-0097]. Emits `altitude:<a>`, `track:<t>`, `sprint:<s>`, and
63
+ * `agent:<t>` (from `assign:agent.<t>`); free-form/unknown constraints (the
64
+ * `other` bucket) produce NO label. Output is deduped and every label is
65
+ * asserted tracker-label-safe ({@link assertLabelSafe}) before return.
66
+ */
67
+ export function programLabels(constraints) {
68
+ const tags = parseConstraintTags(constraints);
69
+ const out = [];
70
+ if (tags.altitude !== undefined)
71
+ out.push(LABEL_MAP.altitude(tags.altitude));
72
+ if (tags.track !== undefined)
73
+ out.push(LABEL_MAP.track(tags.track));
74
+ if (tags.sprint !== undefined)
75
+ out.push(LABEL_MAP.sprint(tags.sprint));
76
+ if (tags.assign !== undefined) {
77
+ const label = LABEL_MAP.assign(tags.assign);
78
+ if (label !== undefined)
79
+ out.push(label);
80
+ }
81
+ const deduped = [...new Set(out)];
82
+ for (const label of deduped)
83
+ assertLabelSafe(label);
84
+ return deduped;
85
+ }
86
+ /** The replayable, task-shaped payload embedded in a program issue body. */
87
+ const PayloadSchema = z.strictObject({
88
+ id: z.string(),
89
+ parent: z.string().optional(),
90
+ goal: z.string(),
91
+ constraints: z.array(z.string()),
92
+ budget: z.object({
93
+ tokens: z.number(),
94
+ usd: z.number(),
95
+ wallClockMin: z.number(),
96
+ }),
97
+ });
98
+ /**
99
+ * Render the GitHub issue body for a decomposed child node (CLM-0097): the
100
+ * node goal as prose, then the replayable task-shaped payload (id, parent,
101
+ * goal, constraints, budget) as a fenced JSON block a human or `kernloop run`
102
+ * can pick up. Pure — no secrets, no tracker, no model. Mirrors the spirit of
103
+ * faculty-observer's `issueBody`.
104
+ */
105
+ export function programIssueBody(node) {
106
+ const payload = PayloadSchema.parse({
107
+ id: node.id,
108
+ ...(node.parent !== undefined ? { parent: node.parent } : {}),
109
+ goal: node.goal,
110
+ constraints: node.constraints,
111
+ budget: node.budget,
112
+ });
113
+ const json = JSON.stringify(payload, null, 2);
114
+ return `${node.goal}\n\n---\nTask-shaped payload — feed to \`kernloop run\` (no privileged path):\n\n\`\`\`json\n${json}\n\`\`\``;
115
+ }
116
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The scrum faculty's registration record (spec §4, §5.4). Parsed through
3
+ * `ManifestSchema` at module load so an invalid manifest fails fast, not at
4
+ * registry time.
5
+ */
6
+ import { type Manifest } from '@kernloop/contracts';
7
+ /**
8
+ * The faculty-scrum registration manifest. Tier is `suggest` — program
9
+ * decomposition stands behind the PM, a generative role, and anything
10
+ * generative enters at `suggest` (spec §3.2); promotion needs evidence +
11
+ * ratification. Consumes TaskContract (the program parent) and emits
12
+ * TaskContract (the derived epic/story children). Cost is zero tokens/usd:
13
+ * decomposition enforcement is mechanical — the PM's generative work runs
14
+ * elsewhere, where the adapters meter it. The capability has no run-executor:
15
+ * it is surfaced through the `kernloop program decompose` CLI verb (CLM-0096),
16
+ * registered here for observability and its ladder tier.
17
+ */
18
+ export declare const scrumManifest: Manifest;
19
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,EAAE,QAuB1B,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The scrum faculty's registration record (spec §4, §5.4). Parsed through
3
+ * `ManifestSchema` at module load so an invalid manifest fails fast, not at
4
+ * registry time.
5
+ */
6
+ import { ManifestSchema } from '@kernloop/contracts';
7
+ /**
8
+ * The faculty-scrum registration manifest. Tier is `suggest` — program
9
+ * decomposition stands behind the PM, a generative role, and anything
10
+ * generative enters at `suggest` (spec §3.2); promotion needs evidence +
11
+ * ratification. Consumes TaskContract (the program parent) and emits
12
+ * TaskContract (the derived epic/story children). Cost is zero tokens/usd:
13
+ * decomposition enforcement is mechanical — the PM's generative work runs
14
+ * elsewhere, where the adapters meter it. The capability has no run-executor:
15
+ * it is surfaced through the `kernloop program decompose` CLI verb (CLM-0096),
16
+ * registered here for observability and its ladder tier.
17
+ */
18
+ export const scrumManifest = ManifestSchema.parse({
19
+ name: '@kernloop/faculty-scrum',
20
+ version: '0.1.0',
21
+ kind: 'faculty',
22
+ capabilities: [
23
+ {
24
+ name: 'scrum.decompose-goal',
25
+ description: 'Decompose a program goal into an epic/story TaskContract tree under the budget-sum invariant with altitude/track/sprint tags (spec §5.4)',
26
+ },
27
+ ],
28
+ contracts: {
29
+ consumes: ['TaskContract'],
30
+ emits: ['TaskContract'],
31
+ },
32
+ cost: {
33
+ tokens: 0,
34
+ usd: 0,
35
+ latencyMs: 10,
36
+ },
37
+ tier: 'suggest',
38
+ claims: ['CLM-0096'],
39
+ maturity: 'stable',
40
+ });
41
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAiB,MAAM,qBAAqB,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAa,cAAc,CAAC,KAAK,CAAC;IAC1D,IAAI,EAAE,yBAAyB;IAC/B,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,SAAS;IACf,YAAY,EAAE;QACZ;YACE,IAAI,EAAE,sBAAsB;YAC5B,WAAW,EACT,0IAA0I;SAC7I;KACF;IACD,SAAS,EAAE;QACT,QAAQ,EAAE,CAAC,cAAc,CAAC;QAC1B,KAAK,EAAE,CAAC,cAAc,CAAC;KACxB;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,SAAS,EAAE,EAAE;KACd;IACD,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,CAAC,UAAU,CAAC;IACpB,QAAQ,EAAE,QAAQ;CACnB,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@kernloop/faculty-scrum",
3
+ "version": "0.1.0",
4
+ "description": "Kernloop scrum/program-decomposition faculty (spec §5.4). Decomposes a goal into an epic/story TaskContract tree one altitude up from the PM's plan decomposition: child budgets must sum within the parent's on every dimension, and each child carries altitude/track/sprint constraint tags. GitHub-free and model-free — pure, mechanical decomposition surfaced through the suggest-tier `kernloop program decompose` CLI.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "zod": "^4.4.3",
20
+ "@kernloop/contracts": "0.1.0"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/kernloop/kernloop.git",
28
+ "directory": "packages/faculty-scrum"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "typecheck": "tsc --noEmit -p tsconfig.json",
33
+ "test": "vitest run --coverage"
34
+ }
35
+ }