@owlmeans/viable-common 0.0.18 → 0.0.20

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.
Files changed (120) hide show
  1. package/README.md +3 -3
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent-presentation/SKILL.md +2 -0
  4. package/agent-meta/skills/viable-common/SKILL.md +195 -10
  5. package/build/ba/consts.d.ts +2 -3
  6. package/build/ba/consts.d.ts.map +1 -1
  7. package/build/ba/consts.js +2 -3
  8. package/build/ba/consts.js.map +1 -1
  9. package/build/ba/helpers.d.ts +2 -1
  10. package/build/ba/helpers.d.ts.map +1 -1
  11. package/build/ba/helpers.js +3 -2
  12. package/build/ba/helpers.js.map +1 -1
  13. package/build/ba/types.d.ts +17 -2
  14. package/build/ba/types.d.ts.map +1 -1
  15. package/build/blueprint/case.js +3 -3
  16. package/build/connect/consts.d.ts +5 -8
  17. package/build/connect/consts.d.ts.map +1 -1
  18. package/build/connect/consts.js +5 -8
  19. package/build/connect/consts.js.map +1 -1
  20. package/build/connect/entrypoints.d.ts +1 -43
  21. package/build/connect/entrypoints.d.ts.map +1 -1
  22. package/build/connect/entrypoints.js +1 -18
  23. package/build/connect/entrypoints.js.map +1 -1
  24. package/build/connect/index.d.ts +1 -0
  25. package/build/connect/index.d.ts.map +1 -1
  26. package/build/connect/index.js +1 -0
  27. package/build/connect/index.js.map +1 -1
  28. package/build/connect/jobs.d.ts +25 -0
  29. package/build/connect/jobs.d.ts.map +1 -0
  30. package/build/connect/jobs.js +29 -0
  31. package/build/connect/jobs.js.map +1 -0
  32. package/build/connect/references.d.ts +1 -40
  33. package/build/connect/references.d.ts.map +1 -1
  34. package/build/connect/references.js +0 -8
  35. package/build/connect/references.js.map +1 -1
  36. package/build/connect/schemas.d.ts +1 -7
  37. package/build/connect/schemas.d.ts.map +1 -1
  38. package/build/connect/schemas.js +1 -25
  39. package/build/connect/schemas.js.map +1 -1
  40. package/build/connect/types.d.ts +30 -45
  41. package/build/connect/types.d.ts.map +1 -1
  42. package/build/design/consts.d.ts +0 -2
  43. package/build/design/consts.d.ts.map +1 -1
  44. package/build/design/consts.js +0 -2
  45. package/build/design/consts.js.map +1 -1
  46. package/build/design/helpers.d.ts +6 -2
  47. package/build/design/helpers.d.ts.map +1 -1
  48. package/build/design/helpers.js +6 -2
  49. package/build/design/helpers.js.map +1 -1
  50. package/build/design/types.d.ts +12 -11
  51. package/build/design/types.d.ts.map +1 -1
  52. package/build/execution/types.d.ts +11 -0
  53. package/build/execution/types.d.ts.map +1 -1
  54. package/build/index.d.ts +1 -0
  55. package/build/index.d.ts.map +1 -1
  56. package/build/index.js +1 -0
  57. package/build/index.js.map +1 -1
  58. package/build/planning/consts.d.ts +141 -0
  59. package/build/planning/consts.d.ts.map +1 -0
  60. package/build/planning/consts.js +170 -0
  61. package/build/planning/consts.js.map +1 -0
  62. package/build/planning/errors.d.ts +64 -0
  63. package/build/planning/errors.d.ts.map +1 -0
  64. package/build/planning/errors.js +99 -0
  65. package/build/planning/errors.js.map +1 -0
  66. package/build/planning/flows.d.ts +34 -0
  67. package/build/planning/flows.d.ts.map +1 -0
  68. package/build/planning/flows.js +174 -0
  69. package/build/planning/flows.js.map +1 -0
  70. package/build/planning/helpers.d.ts +54 -0
  71. package/build/planning/helpers.d.ts.map +1 -0
  72. package/build/planning/helpers.js +101 -0
  73. package/build/planning/helpers.js.map +1 -0
  74. package/build/planning/index.d.ts +7 -0
  75. package/build/planning/index.d.ts.map +1 -0
  76. package/build/planning/index.js +6 -0
  77. package/build/planning/index.js.map +1 -0
  78. package/build/planning/schemas.d.ts +23 -0
  79. package/build/planning/schemas.d.ts.map +1 -0
  80. package/build/planning/schemas.js +65 -0
  81. package/build/planning/schemas.js.map +1 -0
  82. package/build/planning/types.d.ts +70 -0
  83. package/build/planning/types.d.ts.map +1 -0
  84. package/build/planning/types.js +2 -0
  85. package/build/planning/types.js.map +1 -0
  86. package/build/scaffold/schemas.d.ts.map +1 -1
  87. package/build/scaffold/schemas.js +7 -2
  88. package/build/scaffold/schemas.js.map +1 -1
  89. package/build/scaffold/types.d.ts +5 -1
  90. package/build/scaffold/types.d.ts.map +1 -1
  91. package/package.json +8 -6
  92. package/src/ba/consts.ts +2 -3
  93. package/src/ba/helpers.ts +3 -2
  94. package/src/ba/types.ts +17 -2
  95. package/src/blueprint/case.ts +3 -3
  96. package/src/connect/consts.ts +5 -8
  97. package/src/connect/entrypoints.ts +2 -41
  98. package/src/connect/index.ts +1 -0
  99. package/src/connect/jobs.ts +40 -0
  100. package/src/connect/references.ts +1 -22
  101. package/src/connect/schemas.ts +2 -29
  102. package/src/connect/types.ts +30 -51
  103. package/src/design/consts.ts +0 -3
  104. package/src/design/helpers.ts +6 -2
  105. package/src/design/types.ts +12 -11
  106. package/src/execution/types.ts +11 -0
  107. package/src/index.ts +1 -0
  108. package/src/planning/consts.ts +182 -0
  109. package/src/planning/errors.ts +117 -0
  110. package/src/planning/flows.ts +204 -0
  111. package/src/planning/helpers.ts +133 -0
  112. package/src/planning/index.ts +6 -0
  113. package/src/planning/schemas.ts +70 -0
  114. package/src/planning/types.ts +75 -0
  115. package/src/scaffold/schemas.ts +7 -2
  116. package/src/scaffold/types.ts +10 -1
  117. package/tests/branding.spec.ts +37 -0
  118. package/tests/connect-entrypoints.spec.ts +35 -3
  119. package/tests/design.spec.ts +3 -3
  120. package/tests/planning.spec.ts +356 -0
@@ -0,0 +1,117 @@
1
+ import { ResilientError } from '@owlmeans/error'
2
+ import { ResourceError } from '@owlmeans/resource'
3
+
4
+ /**
5
+ * The project and story refusals every process that writes a planning card can raise.
6
+ *
7
+ * Declared HERE rather than by the platform because the viable planning plugin throws them from a
8
+ * process-neutral package, and a refusal class that exists only on one side of a hop cannot be
9
+ * `instanceof`-matched on the other. Type names and markers are wire contracts: the browser
10
+ * phrases a refusal by marker (`viable-project:…`), so neither may change. The bases are declared
11
+ * with them because an error class is unmarshalled by type name with the LAST registration
12
+ * winning — a second declaration of a base anywhere else would replace this one.
13
+ */
14
+ export class ProjectResourceError extends ResourceError {
15
+ public static override typeName = `ViableProject${ResourceError.typeName}`
16
+
17
+ constructor(message: string = 'error') {
18
+ super(`viable-project:${message}`)
19
+ this.type = ProjectResourceError.typeName
20
+ }
21
+ }
22
+
23
+ /** No project card with that id in the caller's organization. */
24
+ export class ProjectNotFound extends ProjectResourceError {
25
+ public static override typeName = `NotFound${ProjectResourceError.typeName}`
26
+
27
+ constructor(message: string = 'error') {
28
+ super(`not-found:${message}`)
29
+ this.type = ProjectNotFound.typeName
30
+ }
31
+ }
32
+
33
+ /** The project exists, and the caller may not act on it. */
34
+ export class ProjectPermissionError extends ProjectResourceError {
35
+ public static override typeName = `Permission${ProjectResourceError.typeName}`
36
+
37
+ constructor(message: string = 'error') {
38
+ super(`permission:${message}`)
39
+ this.type = ProjectPermissionError.typeName
40
+ }
41
+ }
42
+
43
+ export class ProjectStoryError extends ProjectResourceError {
44
+ public static override typeName = `Story${ProjectResourceError.typeName}`
45
+
46
+ constructor(message: string = 'error') {
47
+ super(`story:${message}`)
48
+ this.type = ProjectStoryError.typeName
49
+ }
50
+ }
51
+
52
+ /** No story card with that id or code under the project. */
53
+ export class ProjectStoryNotFound extends ProjectStoryError {
54
+ public static override typeName = `NotFound${ProjectStoryError.typeName}`
55
+
56
+ constructor(message: string = 'error') {
57
+ super(`not-found:${message}`)
58
+ this.type = ProjectStoryNotFound.typeName
59
+ }
60
+ }
61
+
62
+ /**
63
+ * The story is in a state the requested write does not apply to.
64
+ *
65
+ * `wrong-status` is the marker a completed story's refusal carries — the manager matches the whole
66
+ * `viable-project:story:missconfigured:` prefix, so the reason after it is free to grow.
67
+ */
68
+ export class ProjectStoryMissconfigured extends ProjectStoryError {
69
+ public static override typeName = `Missconfigured${ProjectStoryError.typeName}`
70
+
71
+ constructor(message: string = 'error') {
72
+ super(`missconfigured:${message}`)
73
+ this.type = ProjectStoryMissconfigured.typeName
74
+ }
75
+ }
76
+
77
+ export class ProjectError extends ResilientError {
78
+ public static override typeName = `ViableProject${ResilientError.typeName}`
79
+
80
+ constructor(message: string = 'error') {
81
+ super(ProjectError.typeName, `viable-project:${message}`)
82
+ }
83
+ }
84
+
85
+ export class ProjectAgentError extends ProjectError {
86
+ public static override typeName = `Agent${ProjectError.typeName}`
87
+
88
+ constructor(message: string = 'error') {
89
+ super(`agent:${message}`)
90
+ this.type = ProjectAgentError.typeName
91
+ }
92
+ }
93
+
94
+ /**
95
+ * The project's agent is already busy with what was asked for.
96
+ *
97
+ * `story` is the reason a second story start carries: one story of a project is in progress at a
98
+ * time, because a develop run holds the project's sandbox for its whole length.
99
+ */
100
+ export class ProjectAgentOccupied extends ProjectAgentError {
101
+ public static override typeName = `Occupied${ProjectAgentError.typeName}`
102
+
103
+ constructor(message: string = 'error') {
104
+ super(`occupied:${message}`)
105
+ this.type = ProjectAgentOccupied.typeName
106
+ }
107
+ }
108
+
109
+ ResilientError.registerErrorClass(ProjectResourceError)
110
+ ResilientError.registerErrorClass(ProjectNotFound)
111
+ ResilientError.registerErrorClass(ProjectPermissionError)
112
+ ResilientError.registerErrorClass(ProjectStoryError)
113
+ ResilientError.registerErrorClass(ProjectStoryNotFound)
114
+ ResilientError.registerErrorClass(ProjectStoryMissconfigured)
115
+ ResilientError.registerErrorClass(ProjectError)
116
+ ResilientError.registerErrorClass(ProjectAgentError)
117
+ ResilientError.registerErrorClass(ProjectAgentOccupied)
@@ -0,0 +1,204 @@
1
+ import { IntrinsicPolicy, IntrinsicStatus, SpecificationFormat, WorkcardKind } from '@owlmeans/planning'
2
+ import type {
3
+ AnyTypeSchema, ProjectTypeSchema, RelationshipType, SpecificationSlot, StatusFlowSchema,
4
+ WorkcardTypeSchema,
5
+ } from '@owlmeans/planning'
6
+ import { STORY_DESIGN_VERSION } from '../design/consts.js'
7
+ import { StoryDesignSchema } from '../design/schemas.js'
8
+ import { ScaffoldPlanSchema } from '../scaffold/schemas.js'
9
+ import {
10
+ VIABLE_BUG_CODE, VIABLE_BUG_TYPE, VIABLE_DESIGN_REVISIONS_KEPT, VIABLE_IMPROVEMENT_CODE,
11
+ VIABLE_IMPROVEMENT_TYPE, VIABLE_PROJECT_CODE, VIABLE_PROJECT_FLOW, VIABLE_PROJECT_TYPE,
12
+ VIABLE_REQUIREMENT_CODE, VIABLE_REQUIREMENT_TYPE, VIABLE_SPEC_FLOW, VIABLE_SPEC_TYPE,
13
+ VIABLE_STORY_CODE, VIABLE_STORY_FLOW, VIABLE_STORY_TYPE, ViableProjectStatus,
14
+ ViableProjectTransition, ViableRelationship, ViableSpecCategory, ViableSpecStatus,
15
+ ViableStoryStatus, ViableStoryTransition, ViableTone,
16
+ } from './consts.js'
17
+ import {
18
+ ViableOpenFieldsSchema, ViableProjectFieldsSchema, ViableSpecFieldsSchema, ViableStoryFieldsSchema,
19
+ } from './schemas.js'
20
+
21
+ /**
22
+ * The story flow.
23
+ *
24
+ * `failed` maps to PLANNED: a failed story is work still to do, so "done" is the closed count and
25
+ * a retry is simply `start` again. `reset` from anywhere is the manual recovery — the one move a
26
+ * completed story still accepts.
27
+ */
28
+ export const VIABLE_STORY_FLOW_SCHEMA: StatusFlowSchema = {
29
+ id: VIABLE_STORY_FLOW,
30
+ version: 1,
31
+ statuses: [
32
+ { key: ViableStoryStatus.Planned, intrinsic: IntrinsicStatus.Planned, initial: true, tone: ViableTone.Blue },
33
+ { key: ViableStoryStatus.InProgress, intrinsic: IntrinsicStatus.InProgress, tone: ViableTone.Yellow },
34
+ { key: ViableStoryStatus.Completed, intrinsic: IntrinsicStatus.Closed, tone: ViableTone.Green },
35
+ { key: ViableStoryStatus.Failed, intrinsic: IntrinsicStatus.Planned, tone: ViableTone.Red },
36
+ ],
37
+ transitions: [
38
+ {
39
+ name: ViableStoryTransition.Start,
40
+ from: [ViableStoryStatus.Planned, ViableStoryStatus.Failed],
41
+ to: ViableStoryStatus.InProgress,
42
+ explicit: true,
43
+ },
44
+ {
45
+ name: ViableStoryTransition.Complete,
46
+ from: [ViableStoryStatus.InProgress],
47
+ to: ViableStoryStatus.Completed,
48
+ explicit: true,
49
+ },
50
+ {
51
+ name: ViableStoryTransition.Fail,
52
+ from: [ViableStoryStatus.InProgress],
53
+ to: ViableStoryStatus.Failed,
54
+ },
55
+ { name: ViableStoryTransition.Reset, from: '*', to: ViableStoryStatus.Planned, explicit: true },
56
+ ],
57
+ }
58
+
59
+ /**
60
+ * The project flow.
61
+ *
62
+ * A create lands at `draft`; confirming the brief is `confirm`; the end of an initialization is
63
+ * `activate`. A reinitialization and a failed initialization are SLOT facts, not transitions, and
64
+ * destroying a project is a `delete` plus the platform's cascade — never `archive`.
65
+ */
66
+ export const VIABLE_PROJECT_FLOW_SCHEMA: StatusFlowSchema = {
67
+ id: VIABLE_PROJECT_FLOW,
68
+ version: 1,
69
+ statuses: [
70
+ { key: ViableProjectStatus.Draft, intrinsic: IntrinsicStatus.Planned, initial: true, tone: ViableTone.Blue },
71
+ { key: ViableProjectStatus.Confirmed, intrinsic: IntrinsicStatus.InProgress, tone: ViableTone.Yellow },
72
+ { key: ViableProjectStatus.Active, intrinsic: IntrinsicStatus.InProgress, tone: ViableTone.Green },
73
+ { key: ViableProjectStatus.Archived, intrinsic: IntrinsicStatus.Closed, tone: ViableTone.Neutral },
74
+ ],
75
+ transitions: [
76
+ {
77
+ name: ViableProjectTransition.Confirm,
78
+ from: [ViableProjectStatus.Draft],
79
+ to: ViableProjectStatus.Confirmed,
80
+ explicit: true,
81
+ },
82
+ {
83
+ name: ViableProjectTransition.Activate,
84
+ from: [ViableProjectStatus.Confirmed],
85
+ to: ViableProjectStatus.Active,
86
+ },
87
+ { name: ViableProjectTransition.Archive, from: '*', to: ViableProjectStatus.Archived, explicit: true },
88
+ {
89
+ name: ViableProjectTransition.Reopen,
90
+ from: [ViableProjectStatus.Archived],
91
+ to: ViableProjectStatus.Active,
92
+ explicit: true,
93
+ },
94
+ ],
95
+ }
96
+
97
+ /** A document is written or revised; it has no lifecycle to move through. */
98
+ export const VIABLE_SPEC_FLOW_SCHEMA: StatusFlowSchema = {
99
+ id: VIABLE_SPEC_FLOW,
100
+ version: 1,
101
+ statuses: [
102
+ { key: ViableSpecStatus.Current, intrinsic: IntrinsicStatus.Closed, initial: true, tone: ViableTone.Neutral },
103
+ ],
104
+ transitions: [],
105
+ }
106
+
107
+ const slot = (
108
+ category: ViableSpecCategory, format: SpecificationFormat, extra: Partial<SpecificationSlot> = {},
109
+ ): SpecificationSlot => ({ category, format, type: VIABLE_SPEC_TYPE, ...extra })
110
+
111
+ /** The brief a person confirms, and the plan the scaffold draws from it. */
112
+ export const VIABLE_PROJECT_SLOTS: SpecificationSlot[] = [
113
+ slot(ViableSpecCategory.Specification, SpecificationFormat.Markdown, { required: true }),
114
+ slot(ViableSpecCategory.Vision, SpecificationFormat.Markdown),
115
+ slot(ViableSpecCategory.DesignSystem, SpecificationFormat.Markdown),
116
+ slot(ViableSpecCategory.Scaffold, SpecificationFormat.Json, {
117
+ revisioned: true, keepRevisions: VIABLE_DESIGN_REVISIONS_KEPT, schema: ScaffoldPlanSchema,
118
+ }),
119
+ ]
120
+
121
+ /** A story's design — the payload the implementation stage consumes. */
122
+ export const VIABLE_STORY_SLOTS: SpecificationSlot[] = [
123
+ slot(ViableSpecCategory.Design, SpecificationFormat.Json, {
124
+ revisioned: true,
125
+ keepRevisions: VIABLE_DESIGN_REVISIONS_KEPT,
126
+ schema: StoryDesignSchema,
127
+ version: STORY_DESIGN_VERSION,
128
+ }),
129
+ ]
130
+
131
+ const storyLink = (name: ViableRelationship): RelationshipType =>
132
+ ({ name, from: [VIABLE_STORY_TYPE], to: [VIABLE_STORY_TYPE], single: true })
133
+
134
+ export const VIABLE_STORY_RELATIONSHIPS: RelationshipType[] = [
135
+ storyLink(ViableRelationship.Follows),
136
+ storyLink(ViableRelationship.SharesWidget),
137
+ storyLink(ViableRelationship.SharesScreen),
138
+ ]
139
+
140
+ export const VIABLE_PROJECT_TYPE_SCHEMA: ProjectTypeSchema = {
141
+ type: VIABLE_PROJECT_TYPE,
142
+ kind: WorkcardKind.Project,
143
+ version: 1,
144
+ fields: ViableProjectFieldsSchema,
145
+ flows: [VIABLE_PROJECT_FLOW],
146
+ intrinsic: IntrinsicPolicy.Primary,
147
+ specifications: VIABLE_PROJECT_SLOTS,
148
+ code: VIABLE_PROJECT_CODE,
149
+ cardTypes: [VIABLE_STORY_TYPE],
150
+ }
151
+
152
+ export const VIABLE_STORY_TYPE_SCHEMA: WorkcardTypeSchema = {
153
+ type: VIABLE_STORY_TYPE,
154
+ kind: WorkcardKind.Card,
155
+ version: 1,
156
+ fields: ViableStoryFieldsSchema,
157
+ flows: [VIABLE_STORY_FLOW],
158
+ intrinsic: IntrinsicPolicy.Primary,
159
+ specifications: VIABLE_STORY_SLOTS,
160
+ relationships: VIABLE_STORY_RELATIONSHIPS,
161
+ code: VIABLE_STORY_CODE,
162
+ }
163
+
164
+ export const VIABLE_SPEC_TYPE_SCHEMA: WorkcardTypeSchema = {
165
+ type: VIABLE_SPEC_TYPE,
166
+ kind: WorkcardKind.Specification,
167
+ version: 1,
168
+ fields: ViableSpecFieldsSchema,
169
+ flows: [VIABLE_SPEC_FLOW],
170
+ specifications: [],
171
+ }
172
+
173
+ const reserved = (type: string, code: WorkcardTypeSchema['code']): WorkcardTypeSchema => ({
174
+ type,
175
+ kind: WorkcardKind.Card,
176
+ version: 1,
177
+ fields: ViableOpenFieldsSchema,
178
+ flows: [VIABLE_STORY_FLOW],
179
+ intrinsic: IntrinsicPolicy.Primary,
180
+ specifications: [],
181
+ code,
182
+ })
183
+
184
+ /** Registered so their prefixes and flow are decided once; not creatable under a project yet. */
185
+ export const VIABLE_RESERVED_TYPE_SCHEMAS: WorkcardTypeSchema[] = [
186
+ reserved(VIABLE_BUG_TYPE, VIABLE_BUG_CODE),
187
+ reserved(VIABLE_IMPROVEMENT_TYPE, VIABLE_IMPROVEMENT_CODE),
188
+ reserved(VIABLE_REQUIREMENT_TYPE, VIABLE_REQUIREMENT_CODE),
189
+ ]
190
+
191
+ /** Every Viable type — what a planning plugin registers. */
192
+ export const VIABLE_TYPE_SCHEMAS: AnyTypeSchema[] = [
193
+ VIABLE_PROJECT_TYPE_SCHEMA,
194
+ VIABLE_STORY_TYPE_SCHEMA,
195
+ VIABLE_SPEC_TYPE_SCHEMA,
196
+ ...VIABLE_RESERVED_TYPE_SCHEMAS,
197
+ ]
198
+
199
+ /** Every Viable flow — registered beside the types. */
200
+ export const VIABLE_FLOW_SCHEMAS: StatusFlowSchema[] = [
201
+ VIABLE_PROJECT_FLOW_SCHEMA,
202
+ VIABLE_STORY_FLOW_SCHEMA,
203
+ VIABLE_SPEC_FLOW_SCHEMA,
204
+ ]
@@ -0,0 +1,133 @@
1
+ import { WorkcardKind } from '@owlmeans/planning'
2
+ import type { Specification, Workcard } from '@owlmeans/planning'
3
+ import { ProjectArea } from '../areas/consts.js'
4
+ import type { StoryDraft } from '../areas/types.js'
5
+ import type { AgentProject, UserStory } from '../ba/types.js'
6
+ import { userStoryOfDesign } from '../design/helpers.js'
7
+ import type { StoryDesign } from '../design/types.js'
8
+ import type { StoryWriteInput } from '../metadata/types.js'
9
+ import {
10
+ VIABLE_PROJECT_TYPE, VIABLE_STORY_TYPE, VIABLE_USER_CHANNELS, ViableSpecCategory,
11
+ } from './consts.js'
12
+ import { ProjectStoryMissconfigured } from './errors.js'
13
+ import type {
14
+ StoryWriteContent, ViableProjectCard, ViableProjectFields, ViableStoryCard, ViableStoryFields,
15
+ } from './types.js'
16
+
17
+ /** A `viable:project` card — kind AND type, since a foreign provider may share either alone. */
18
+ export const isViableProject = (card?: Workcard | null): card is ViableProjectCard =>
19
+ card != null && card.kind === WorkcardKind.Project && card.type === VIABLE_PROJECT_TYPE
20
+
21
+ /** A `viable:user-story` card. */
22
+ export const isViableStory = (card?: Workcard | null): card is ViableStoryCard =>
23
+ card != null && card.kind === WorkcardKind.Card && card.type === VIABLE_STORY_TYPE
24
+
25
+ /** Whether a write came from a person (`web`, `connect`) rather than the platform's own runs. */
26
+ export const isUserChannel = (channel?: string | null): boolean =>
27
+ channel != null && VIABLE_USER_CHANNELS.includes(channel)
28
+
29
+ export const projectFieldsOf = (card: Workcard): ViableProjectFields =>
30
+ (card.fields ?? {}) as ViableProjectFields
31
+
32
+ /**
33
+ * A story card's fields, with the two required ones always answered.
34
+ *
35
+ * An absent area reads as `guest` — the area that guards nothing — and an absent primary flag as
36
+ * `false`, so a reader never branches on a card written by a store that dropped them.
37
+ */
38
+ export const storyFieldsOf = (card: Workcard): ViableStoryFields => {
39
+ const fields = (card.fields ?? {}) as Partial<ViableStoryFields>
40
+
41
+ return { ...fields, area: fields.area ?? ProjectArea.Guest, primary: fields.primary === true }
42
+ }
43
+
44
+ /** The draft shape the analysis prompts and moderation take, from a card. */
45
+ export const storyDraftOf = (card: Workcard): StoryDraft =>
46
+ ({ story: card.title, area: storyFieldsOf(card).area })
47
+
48
+ /**
49
+ * The aggregate the coders take, for a story CARD.
50
+ *
51
+ * The card wins on the narrative, the code, the area and the actor — a person may have changed any
52
+ * of them since the design was written. The design supplies what only it knows: the entities and
53
+ * the screens. Without a design the aggregate has no screens yet.
54
+ */
55
+ export const userStoryOf = (card: Workcard, design?: StoryDesign | null): UserStory => {
56
+ const fields = storyFieldsOf(card)
57
+ const base: UserStory = design != null
58
+ ? userStoryOfDesign(design)
59
+ : { story: card.title, area: fields.area, entity: null as unknown as string, entities: [], screens: [] }
60
+ const code = card.code ?? base.code
61
+
62
+ return {
63
+ ...base,
64
+ story: card.title,
65
+ ...(code != null ? { code } : {}),
66
+ area: fields.area,
67
+ ...(fields.actor != null ? { actor: fields.actor } : {}),
68
+ }
69
+ }
70
+
71
+ /**
72
+ * The current document of one category among a card's specifications.
73
+ *
74
+ * The highest revision wins, then the most recently updated; a category nobody wrote answers
75
+ * `null`.
76
+ */
77
+ export const currentSpecOf = (
78
+ specs: readonly Specification[], category: string,
79
+ ): Specification | null =>
80
+ specs
81
+ .filter(spec => spec.category === category)
82
+ .reduce<Specification | null>((best, spec) => {
83
+ if (best == null) return spec
84
+ const byRevision = (spec.revision ?? 0) - (best.revision ?? 0)
85
+ if (byRevision !== 0) return byRevision > 0 ? spec : best
86
+
87
+ return (spec.updatedAt ?? spec.createdAt) > (best.updatedAt ?? best.createdAt) ? spec : best
88
+ }, null)
89
+
90
+ /** The body of a category's current document, `''` when there is none. */
91
+ export const currentSpecBody = (specs: readonly Specification[], category: string): string =>
92
+ currentSpecOf(specs, category)?.body ?? ''
93
+
94
+ /**
95
+ * The brief a generator reads, assembled from the project card and its specifications.
96
+ *
97
+ * `name` is the card's title and `alias` its code; a brief part nobody wrote is `''`, which is
98
+ * what every prompt already treats as "not stated".
99
+ */
100
+ export const projectBriefOf = (project: Workcard, specs: readonly Specification[]): AgentProject => {
101
+ const { language } = projectFieldsOf(project)
102
+
103
+ return {
104
+ name: project.title,
105
+ ...(project.code != null ? { alias: project.code } : {}),
106
+ description: project.description ?? '',
107
+ specification: currentSpecBody(specs, ViableSpecCategory.Specification),
108
+ designSystem: currentSpecBody(specs, ViableSpecCategory.DesignSystem),
109
+ vision: currentSpecBody(specs, ViableSpecCategory.Vision),
110
+ ...(language != null ? { language } : {}),
111
+ }
112
+ }
113
+
114
+ /**
115
+ * What the metadata store writes to `docs/stories/<code>.md`, from the card and a run's content.
116
+ *
117
+ * The CODE is the target's only story identifier — a card id never reaches a target file — so a
118
+ * story card without one is refused rather than filed under its id.
119
+ */
120
+ export const storyWriteInputOf = (card: Workcard, content: StoryWriteContent): StoryWriteInput => {
121
+ if (card.code == null || card.code === '') {
122
+ throw new ProjectStoryMissconfigured('no-code')
123
+ }
124
+ const { status, ...rest } = content
125
+
126
+ return {
127
+ ...rest,
128
+ code: card.code,
129
+ narrative: card.title,
130
+ status: status ?? card.status,
131
+ primary: storyFieldsOf(card).primary,
132
+ }
133
+ }
@@ -0,0 +1,6 @@
1
+ export * from './consts.js'
2
+ export * from './errors.js'
3
+ export * from './schemas.js'
4
+ export * from './flows.js'
5
+ export * from './helpers.js'
6
+ export type * from './types.js'
@@ -0,0 +1,70 @@
1
+ import type { JSONSchemaType } from 'ajv'
2
+ import { CODE_MAX } from '@owlmeans/planning'
3
+ import { ProjectArea } from '../areas/consts.js'
4
+ import { StoryKind } from '../ba/consts.js'
5
+ import { ConnectLlm, ConnectTarget } from '../connect/consts.js'
6
+ import { ProjectOriginSchema } from '../convert/schemas.js'
7
+ import { StoryActor } from '../design/runtime.js'
8
+ import type { ViableProjectFields, ViableStoryFields } from './types.js'
9
+
10
+ /**
11
+ * The `fields` schema of a `viable:project` card.
12
+ *
13
+ * A record-element schema: hand-written, cast, every optional key nullable with its type, every
14
+ * nullable enum listing `null`. Plain strings where the vocabulary grows in a library this record
15
+ * does not redeploy with (blueprint, case, game kind).
16
+ */
17
+ export const ViableProjectFieldsSchema = {
18
+ type: 'object',
19
+ properties: {
20
+ formerAliases: {
21
+ type: 'array', items: { type: 'string', minLength: 1, maxLength: CODE_MAX }, nullable: true,
22
+ },
23
+ language: { type: 'string', minLength: 2, maxLength: 16, nullable: true },
24
+ blueprint: { type: 'string', minLength: 1, maxLength: 64, nullable: true },
25
+ blueprintCase: { type: 'string', minLength: 1, maxLength: 64, nullable: true },
26
+ gameKind: { type: 'string', minLength: 1, maxLength: 64, nullable: true },
27
+ target: { type: 'string', enum: [...Object.values(ConnectTarget), null], nullable: true },
28
+ origin: { ...ProjectOriginSchema, nullable: true },
29
+ connectLlmMode: { type: 'string', enum: [...Object.values(ConnectLlm), null], nullable: true },
30
+ converterLlmMode: { type: 'string', enum: [...Object.values(ConnectLlm), null], nullable: true },
31
+ },
32
+ required: [],
33
+ additionalProperties: false,
34
+ } as unknown as JSONSchemaType<ViableProjectFields>
35
+
36
+ /**
37
+ * The `fields` schema of a `viable:user-story` card.
38
+ *
39
+ * `area` is required, and a create that carries none is still valid by the time it is checked:
40
+ * validation runs after the planning middlewares, and the viable plugin's re-format hook fills the
41
+ * area of a narrative a person wrote.
42
+ */
43
+ export const ViableStoryFieldsSchema = {
44
+ type: 'object',
45
+ properties: {
46
+ area: { type: 'string', enum: Object.values(ProjectArea) },
47
+ primary: { type: 'boolean' },
48
+ actor: { type: 'string', enum: [...Object.values(StoryActor), null], nullable: true },
49
+ warning: { type: 'string', maxLength: 1024, nullable: true },
50
+ kind: { type: 'string', enum: [...Object.values(StoryKind), null], nullable: true },
51
+ },
52
+ required: ['area', 'primary'],
53
+ additionalProperties: false,
54
+ } as unknown as JSONSchemaType<ViableStoryFields>
55
+
56
+ /** The open `fields` of a reserved card type — nothing is decided about them yet. */
57
+ export const ViableOpenFieldsSchema = {
58
+ type: 'object',
59
+ properties: {},
60
+ required: [],
61
+ additionalProperties: true,
62
+ } as unknown as JSONSchemaType<Record<string, unknown>>
63
+
64
+ /** A document carries nothing under `fields`: its payload is the specification body. */
65
+ export const ViableSpecFieldsSchema = {
66
+ type: 'object',
67
+ properties: {},
68
+ required: [],
69
+ additionalProperties: false,
70
+ } as unknown as JSONSchemaType<Record<string, never>>
@@ -0,0 +1,75 @@
1
+ import type { Card, Project, Specification } from '@owlmeans/planning'
2
+ import type { ProjectArea } from '../areas/consts.js'
3
+ import type { StoryKind } from '../ba/consts.js'
4
+ import type { ConnectLlm, ConnectTarget } from '../connect/consts.js'
5
+ import type { ProjectOrigin } from '../convert/types.js'
6
+ import type { StoryActor } from '../design/runtime.js'
7
+ import type { StoryWriteInput } from '../metadata/types.js'
8
+
9
+ /**
10
+ * What a `viable:project` card keeps under `fields`.
11
+ *
12
+ * The alias is the card's `code`, the name its `title`; the brief parts are specifications. Every
13
+ * key is optional: a project created before a field existed carries none, and an absent value
14
+ * means the platform default (inherit a mode, the default blueprint, a cloud slot).
15
+ */
16
+ export type ViableProjectFields = {
17
+ /** Aliases this project gave up. Names are retired, never freed to a sibling project. */
18
+ formerAliases?: string[]
19
+ /** The IETF tag generated UI text is written in. */
20
+ language?: string
21
+ /** The blueprint id the project was drawn with. */
22
+ blueprint?: string
23
+ /** The blueprint case classified from the prompt. Absent applies no patch. */
24
+ blueprintCase?: string
25
+ /** How a game is played; absent for every other case. */
26
+ gameKind?: string
27
+ /** Where the tree lives; absent is the platform's own slot. */
28
+ target?: ConnectTarget
29
+ /** Where converted code came from; present only on an import. */
30
+ origin?: ProjectOrigin
31
+ /** Who performs a run's model calls on this project; absent inherits the owner's preference. */
32
+ connectLlmMode?: ConnectLlm
33
+ /** Who performs a conversion's model calls on this project; absent inherits. */
34
+ converterLlmMode?: ConnectLlm
35
+ }
36
+
37
+ /**
38
+ * What a `viable:user-story` card keeps under `fields`.
39
+ *
40
+ * The narrative is the card's `title`, the flow ordinal its `order` (a double — a connective story
41
+ * sits between two flow steps), the status its primary flow status.
42
+ */
43
+ export type ViableStoryFields = {
44
+ /** The audience acting in the story; its screens hang under this area. */
45
+ area: ProjectArea
46
+ /** The story whose screen is the project's front door. Exactly one per project. */
47
+ primary: boolean
48
+ /** Who acts; absent is a person. */
49
+ actor?: StoryActor
50
+ /** Why the last development did not end cleanly. Written as `''` to clear. */
51
+ warning?: string
52
+ /** Which half of the analysis the story came from. */
53
+ kind?: StoryKind
54
+ }
55
+
56
+ export interface ViableProjectCard extends Project {
57
+ fields: ViableProjectFields
58
+ }
59
+
60
+ export interface ViableStoryCard extends Card {
61
+ fields: ViableStoryFields
62
+ }
63
+
64
+ /** A document under a Viable card — the brief parts, the scaffold plan, a story design. */
65
+ export type ViableSpecification = Specification
66
+
67
+ /**
68
+ * What a develop run hands the metadata store beside the card.
69
+ *
70
+ * The code, the narrative and the primary flag are the CARD's and are never passed separately — a
71
+ * second copy is a second answer. `status` defaults to the card's own.
72
+ */
73
+ export type StoryWriteContent =
74
+ & Omit<StoryWriteInput, 'code' | 'narrative' | 'primary' | 'status'>
75
+ & { status?: string }
@@ -168,15 +168,20 @@ const GuestHomePlanSchema: JSONSchemaType<GuestHomePlan> = {
168
168
  hero: {
169
169
  type: 'object',
170
170
  properties: {
171
- eyebrow: { type: 'string', description: 'A 2-4 word positioning phrase above the headline' },
172
171
  headline: {
173
172
  type: 'string',
174
173
  description: 'The product\'s promise in one line, under 9 words. Never the product name alone'
175
174
  },
176
175
  sub: { type: 'string', description: 'One or two sentences: what it does and who it is for' },
177
176
  cta: { type: 'string', description: 'The call-to-action label, 2-3 words' },
177
+ eyebrow: {
178
+ type: 'string', nullable: true,
179
+ description: 'OPTIONAL, normally omitted. A small badge above the headline - set it only when'
180
+ + ' the specification explicitly asks for a label there. A landing page carries no badge by'
181
+ + ' default: the headline is what positions the product'
182
+ },
178
183
  },
179
- required: ['eyebrow', 'headline', 'sub', 'cta'],
184
+ required: ['headline', 'sub', 'cta'],
180
185
  additionalProperties: false,
181
186
  },
182
187
  problem: {
@@ -91,7 +91,16 @@ export interface ProductIdentity {
91
91
 
92
92
  /** The guest area's landing page — the product's public face. */
93
93
  export interface GuestHomePlan {
94
- hero: { eyebrow: string, headline: string, sub: string, cta: string }
94
+ hero: {
95
+ headline: string
96
+ sub: string
97
+ cta: string
98
+ /**
99
+ * A badge above the headline — OPTIONAL and normally absent. A landing page carries no badge
100
+ * unless its specification asks for one; the headline is what positions the product.
101
+ */
102
+ eyebrow?: string
103
+ }
95
104
  problem: { title: string, text: string }
96
105
  solution: { title: string, text: string }
97
106
  features: { title: string, text: string }[]