laymos 0.0.6 → 0.0.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../../src/cli/lint/lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAQ9C,wBAAgB,eAAe,CAAC,CAAC,EAC/B,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,+QAW5C"}
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../../src/cli/lint/lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAa9C,wBAAgB,eAAe,CAAC,CAAC,EAC/B,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,+QAW5C"}
@@ -1,10 +1,12 @@
1
1
  import { Console, Effect } from 'effect';
2
2
  import { Command } from 'effect/unstable/cli';
3
3
  import { analyzeProject } from '../../orchestrator/analyze-project/index.js';
4
+ import { getStoryTree } from '../../orchestrator/run-stories/index.js';
4
5
  import { makeLayersCommand } from './layers/index.js';
5
6
  import { renderLayerReport } from './layers/report.js';
6
7
  import { makeModulesCommand } from './modules/index.js';
7
8
  import { renderModuleReport } from './modules/report.js';
9
+ import { countGroupsWithoutPage, renderStoriesReport, } from './stories/index.js';
8
10
  export function makeLintCommand(configPath) {
9
11
  return Command.make('lint', {}, () => configPath.pipe(Effect.flatMap(runLint))).pipe(Command.withDescription('Check every configured architectural rule.'), Command.withSubcommands([
10
12
  makeLayersCommand(configPath),
@@ -16,11 +18,19 @@ function runLint(configPath) {
16
18
  const result = yield* analyzeProject(configPath);
17
19
  yield* Console.log(renderLayerReport(result.layerAnalysis));
18
20
  yield* Console.log(renderModuleReport(result.moduleAnalysis));
21
+ const missingPages = yield* lintStories(configPath);
19
22
  if (result.layerAnalysis.unassignedFiles.length > 0 ||
20
23
  result.layerAnalysis.forbiddenImports.length > 0 ||
21
24
  result.layerAnalysis.layersWithoutModules.length > 0 ||
22
- result.moduleAnalysis.violations.length > 0) {
25
+ result.moduleAnalysis.violations.length > 0 ||
26
+ missingPages > 0) {
23
27
  process.exitCode = 1;
24
28
  }
25
29
  });
26
30
  }
31
+ // A project without a Stories path has no Story Groups to lint.
32
+ function lintStories(configPath) {
33
+ return getStoryTree(configPath).pipe(Effect.flatMap((tree) => Console.log(renderStoriesReport(tree)).pipe(Effect.as(countGroupsWithoutPage(tree)))), Effect.catchTag('StoriesError', (error) => error.reason === 'no-stories-path'
34
+ ? Effect.succeed(0)
35
+ : Effect.die(error)));
36
+ }
@@ -0,0 +1,2 @@
1
+ export { countGroupsWithoutPage, renderStoriesReport } from './report.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/lint/stories/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ // The lint command uses these to report Story Groups missing their page.
2
+ export { countGroupsWithoutPage, renderStoriesReport } from './report.js';
@@ -0,0 +1,4 @@
1
+ import type { StoryTree } from '../../../story/schema/index.js';
2
+ export declare function renderStoriesReport(tree: StoryTree): string;
3
+ export declare function countGroupsWithoutPage(tree: StoryTree): number;
4
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../../src/cli/lint/stories/report.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,gCAAgC,CAAC;AAEhF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAY3D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAE9D"}
@@ -0,0 +1,24 @@
1
+ import * as colors from 'yoctocolors';
2
+ export function renderStoriesReport(tree) {
3
+ const missing = groupsWithoutPage(tree, []);
4
+ if (missing.length === 0) {
5
+ return colors.green('✓ Every Story Group has a page');
6
+ }
7
+ return [
8
+ colors.red('Story Groups without a page'),
9
+ '',
10
+ ...missing.map((id) => ` ${colors.yellow('✕')} ${id}`),
11
+ '',
12
+ `${missing.length} ${missing.length === 1 ? 'group' : 'groups'}`,
13
+ ].join('\n');
14
+ }
15
+ export function countGroupsWithoutPage(tree) {
16
+ return groupsWithoutPage(tree, []).length;
17
+ }
18
+ function groupsWithoutPage(group, path) {
19
+ const id = [...path, group.title];
20
+ return [
21
+ ...(group.page === null ? [id.join('/')] : []),
22
+ ...group.groups.flatMap((child) => groupsWithoutPage(child, id)),
23
+ ];
24
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"run-stories.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/run-stories/run-stories.ts"],"names":[],"mappings":"AAIA,OAAO,EAAS,MAAM,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAC;AAWrD,OAAO,KAAK,EAOV,WAAW,EAEX,SAAS,EAEV,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAUlE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,GAAG,YAAY,CAAC,CAEtD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;CAC9C;AAID,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,WAAW,GAAG,YAAY,CAAC,CAYvD;AAED,wBAAgB,UAAU,CACxB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,CAAC,CAIxD"}
1
+ {"version":3,"file":"run-stories.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/run-stories/run-stories.ts"],"names":[],"mappings":"AAIA,OAAO,EAAS,MAAM,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAC;AAWrD,OAAO,KAAK,EAQV,WAAW,EAEX,SAAS,EAEV,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAUlE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,GAAG,YAAY,CAAC,CAEtD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;CAC9C;AAID,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,WAAW,GAAG,YAAY,CAAC,CAYvD;AAED,wBAAgB,UAAU,CACxB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,CAAC,CAIxD"}
@@ -65,11 +65,17 @@ function loadRoot(configPath) {
65
65
  cause: null,
66
66
  });
67
67
  }
68
- return yield* indexGroup(root, [root.title], entryPoint, {
68
+ const options = {
69
69
  projectRoot: dirname(absoluteConfigPath),
70
70
  storiesRoot,
71
71
  supportCache: new Map(),
72
- });
72
+ groupDirectories: new Map(),
73
+ };
74
+ const indexed = yield* indexGroup(root, [root.title], entryPoint, options);
75
+ return {
76
+ ...indexed,
77
+ tree: attachGroupPages(indexed.tree, indexed.directory, options.projectRoot, options.groupDirectories),
78
+ };
73
79
  }).pipe(Effect.provide(ConfigServiceLive), Effect.provide(NodeServices.layer));
74
80
  }
75
81
  function indexGroup(group, path, entryPoint, options) {
@@ -78,6 +84,7 @@ function indexGroup(group, path, entryPoint, options) {
78
84
  const groups = [];
79
85
  const leaves = [];
80
86
  const stories = [];
87
+ const directories = [];
81
88
  for (const child of group.children) {
82
89
  const id = [...path, child.title].join('/');
83
90
  if (isStory(child)) {
@@ -87,25 +94,102 @@ function indexGroup(group, path, entryPoint, options) {
87
94
  leaves.push({
88
95
  id,
89
96
  title: child.title,
97
+ description: child.description,
98
+ spine: child.spine,
99
+ page: resolveStoryPage(child, options.projectRoot),
90
100
  setup: snippets.setup,
91
101
  questions,
92
102
  source,
93
103
  support: resolveSupport(child, options),
94
104
  });
95
105
  stories.push({ id, story: child });
106
+ const directory = storyDirectory(child);
107
+ if (directory !== null)
108
+ directories.push(directory);
96
109
  }
97
110
  else {
98
111
  const indexed = yield* indexGroup(child, [...path, child.title], entryPoint, options);
99
112
  groups.push(indexed.tree);
113
+ options.groupDirectories.set(indexed.tree, indexed.directory);
100
114
  stories.push(...indexed.stories);
115
+ directories.push(...indexed.directories);
101
116
  }
102
117
  }
103
118
  return {
104
- tree: { title: group.title, groups, stories: leaves },
119
+ tree: {
120
+ title: group.title,
121
+ description: group.description,
122
+ page: null,
123
+ groups,
124
+ stories: leaves,
125
+ },
105
126
  stories,
127
+ directories,
128
+ directory: commonDirectory(directories),
106
129
  };
107
130
  });
108
131
  }
132
+ function storyDirectory(story) {
133
+ try {
134
+ return dirname(fileURLToPath(story.sourceUrl));
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ function readPage(filePath, projectRoot) {
141
+ if (!existsSync(filePath))
142
+ return null;
143
+ return {
144
+ path: relative(projectRoot, filePath),
145
+ content: readFileSync(filePath, 'utf8'),
146
+ };
147
+ }
148
+ function resolveStoryPage(story, projectRoot) {
149
+ let filePath;
150
+ try {
151
+ filePath = fileURLToPath(story.sourceUrl);
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ if (!filePath.endsWith('.ts'))
157
+ return null;
158
+ return readPage(`${filePath.slice(0, -3)}.md`, projectRoot);
159
+ }
160
+ // A Story Group owns the folder its descendant Stories share, and its page sits
161
+ // there under the group's own title. Two groups can span one folder — a Learn
162
+ // and a Reference trunk over the same Stories — so the title, not `index`, is
163
+ // what keeps their pages apart.
164
+ function groupPage(group, directory, projectRoot) {
165
+ if (directory === null)
166
+ return null;
167
+ return readPage(join(directory, `${slugifyQuestion(group.title)}.md`), projectRoot);
168
+ }
169
+ function attachGroupPages(group, directory, projectRoot, directoriesById) {
170
+ return {
171
+ ...group,
172
+ page: groupPage(group, directory, projectRoot),
173
+ groups: group.groups.map((child) => attachGroupPages(child, directoriesById.get(child) ?? null, projectRoot, directoriesById)),
174
+ };
175
+ }
176
+ function commonDirectory(directories) {
177
+ const [first, ...rest] = directories;
178
+ if (first === undefined)
179
+ return null;
180
+ let shared = first.split(sep);
181
+ for (const directory of rest) {
182
+ const segments = directory.split(sep);
183
+ let index = 0;
184
+ while (index < shared.length &&
185
+ index < segments.length &&
186
+ shared[index] === segments[index]) {
187
+ index += 1;
188
+ }
189
+ shared = shared.slice(0, index);
190
+ }
191
+ return shared.length === 0 ? null : shared.join(sep);
192
+ }
109
193
  function indexQuestions(story, source, snippets) {
110
194
  return Effect.gen(function* () {
111
195
  const seen = new Set();
@@ -1,6 +1,6 @@
1
1
  export { CapturedTraceSchema, QuestionReportSchema, QuestionSectionSchema, RecordedFlowSchema, StoryAssertionSchema, StoryReportSchema, StoryVerdictSchema, } from './story-report-schema.js';
2
2
  export type { CapturedTrace, JsonValue, QuestionReport, QuestionSection, RecordedFlow, StoryAssertion, StoryReport, StoryVerdict, } from './story-report-schema.js';
3
3
  export { slugifyQuestion } from './slug.js';
4
- export { QuestionLeafSchema, StoryLeafSchema, StorySourceSchema, StoryTreeGroupSchema, StoryTreeSchema, } from './story-tree-schema.js';
5
- export type { QuestionLeaf, StoryLeaf, StorySource, StoryTree, StoryTreeGroup, } from './story-tree-schema.js';
4
+ export { QuestionLeafSchema, StoryLeafSchema, StoryPageSchema, StorySourceSchema, StoryTreeGroupSchema, StoryTreeSchema, } from './story-tree-schema.js';
5
+ export type { QuestionLeaf, StoryLeaf, StoryPage, StorySource, StoryTree, StoryTreeGroup, } from './story-tree-schema.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/story/schema/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,aAAa,EACb,SAAS,EACT,cAAc,EACd,eAAe,EACf,YAAY,EACZ,cAAc,EACd,WAAW,EACX,YAAY,GACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,SAAS,EACT,cAAc,GACf,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/story/schema/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,aAAa,EACb,SAAS,EACT,cAAc,EACd,eAAe,EACf,YAAY,EACZ,cAAc,EACd,WAAW,EACX,YAAY,GACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,YAAY,EACZ,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,cAAc,GACf,MAAM,wBAAwB,CAAC"}
@@ -1,4 +1,4 @@
1
1
  // RPC transports and renderers use this browser-safe Story report contract.
2
2
  export { CapturedTraceSchema, QuestionReportSchema, QuestionSectionSchema, RecordedFlowSchema, StoryAssertionSchema, StoryReportSchema, StoryVerdictSchema, } from './story-report-schema.js';
3
3
  export { slugifyQuestion } from './slug.js';
4
- export { QuestionLeafSchema, StoryLeafSchema, StorySourceSchema, StoryTreeGroupSchema, StoryTreeSchema, } from './story-tree-schema.js';
4
+ export { QuestionLeafSchema, StoryLeafSchema, StoryPageSchema, StorySourceSchema, StoryTreeGroupSchema, StoryTreeSchema, } from './story-tree-schema.js';
@@ -3,6 +3,10 @@ export declare const StorySourceSchema: Schema.Struct<{
3
3
  readonly path: Schema.String;
4
4
  readonly content: Schema.String;
5
5
  }>;
6
+ export declare const StoryPageSchema: Schema.Struct<{
7
+ readonly path: Schema.String;
8
+ readonly content: Schema.String;
9
+ }>;
6
10
  export declare const QuestionLeafSchema: Schema.Struct<{
7
11
  readonly slug: Schema.String;
8
12
  readonly question: Schema.String;
@@ -12,6 +16,12 @@ export declare const QuestionLeafSchema: Schema.Struct<{
12
16
  export declare const StoryLeafSchema: Schema.Struct<{
13
17
  readonly id: Schema.String;
14
18
  readonly title: Schema.String;
19
+ readonly description: Schema.String;
20
+ readonly spine: Schema.Boolean;
21
+ readonly page: Schema.NullOr<Schema.Struct<{
22
+ readonly path: Schema.String;
23
+ readonly content: Schema.String;
24
+ }>>;
15
25
  readonly setup: Schema.NullOr<Schema.String>;
16
26
  readonly questions: Schema.$Array<Schema.Struct<{
17
27
  readonly slug: Schema.String;
@@ -30,6 +40,8 @@ export declare const StoryLeafSchema: Schema.Struct<{
30
40
  }>;
31
41
  export interface StoryTreeGroup {
32
42
  readonly title: string;
43
+ readonly description: string;
44
+ readonly page: typeof StoryPageSchema.Type | null;
33
45
  readonly groups: readonly StoryTreeGroup[];
34
46
  readonly stories: readonly (typeof StoryLeafSchema.Type)[];
35
47
  }
@@ -38,5 +50,6 @@ export declare const StoryTreeSchema: Schema.Codec<StoryTreeGroup, StoryTreeGrou
38
50
  export type QuestionLeaf = typeof QuestionLeafSchema.Type;
39
51
  export type StoryLeaf = typeof StoryLeafSchema.Type;
40
52
  export type StorySource = typeof StorySourceSchema.Type;
53
+ export type StoryPage = typeof StoryPageSchema.Type;
41
54
  export type StoryTree = StoryTreeGroup;
42
55
  //# sourceMappingURL=story-tree-schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"story-tree-schema.d.ts","sourceRoot":"","sources":["../../../src/story/schema/story-tree-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,iBAAiB;;;EAO5B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;EAS7B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;EAW1B,CAAC;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;CAC5D;AAED,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,CAUjB,CAAC;AAE9C,eAAO,MAAM,eAAe,4DAI1B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAC;AAC1D,MAAM,MAAM,SAAS,GAAG,OAAO,eAAe,CAAC,IAAI,CAAC;AACpD,MAAM,MAAM,WAAW,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAC;AACxD,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC"}
1
+ {"version":3,"file":"story-tree-schema.d.ts","sourceRoot":"","sources":["../../../src/story/schema/story-tree-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,iBAAiB;;;EAO5B,CAAC;AAEH,eAAO,MAAM,eAAe;;;EAO1B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;EAS7B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;EAc1B,CAAC;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,eAAe,CAAC,IAAI,GAAG,IAAI,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;CAC5D;AAED,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,CAYjB,CAAC;AAE9C,eAAO,MAAM,eAAe,4DAI1B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAC;AAC1D,MAAM,MAAM,SAAS,GAAG,OAAO,eAAe,CAAC,IAAI,CAAC;AACpD,MAAM,MAAM,WAAW,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAC;AACxD,MAAM,MAAM,SAAS,GAAG,OAAO,eAAe,CAAC,IAAI,CAAC;AACpD,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC"}
@@ -6,6 +6,13 @@ export const StorySourceSchema = Schema.Struct({
6
6
  title: 'Story Source',
7
7
  description: 'The full text of a project file backing a Story: the file the Story was authored in, or the support file its proofs import.',
8
8
  });
9
+ export const StoryPageSchema = Schema.Struct({
10
+ path: Schema.String,
11
+ content: Schema.String,
12
+ }).annotate({
13
+ title: 'Story Page',
14
+ description: "The markdown page narrating a Story or a Story Group: a Story's sibling `.md` file, or a Story Group's `index.md`. Null when the author wrote none.",
15
+ });
9
16
  export const QuestionLeafSchema = Schema.Struct({
10
17
  slug: Schema.String,
11
18
  question: Schema.String,
@@ -18,21 +25,26 @@ export const QuestionLeafSchema = Schema.Struct({
18
25
  export const StoryLeafSchema = Schema.Struct({
19
26
  id: Schema.String,
20
27
  title: Schema.String,
28
+ description: Schema.String,
29
+ spine: Schema.Boolean,
30
+ page: Schema.NullOr(StoryPageSchema),
21
31
  setup: Schema.NullOr(Schema.String),
22
32
  questions: Schema.Array(QuestionLeafSchema),
23
33
  source: StorySourceSchema,
24
34
  support: Schema.NullOr(StorySourceSchema),
25
35
  }).annotate({
26
36
  title: 'Story Leaf',
27
- description: 'One Story in the Story tree: its derived id, the shared setup snippet, its Questions in authored order, and the support file its proofs import. Never carries results.',
37
+ description: 'One Story in the Story tree: its derived id, its one-line description, whether it sits on the spine, its markdown page, the shared setup snippet, its Questions in authored order, and the support file its proofs import. Never carries results.',
28
38
  });
29
39
  export const StoryTreeGroupSchema = Schema.Struct({
30
40
  title: Schema.String,
41
+ description: Schema.String,
42
+ page: Schema.NullOr(StoryPageSchema),
31
43
  groups: Schema.Array(Schema.suspend(() => StoryTreeGroupSchema)),
32
44
  stories: Schema.Array(StoryLeafSchema),
33
45
  }).annotate({
34
46
  title: 'Story Group',
35
- description: 'One node of the Story tree: a title over any mix of subgroups and Story leaves.',
47
+ description: 'One node of the Story tree: a title, a one-line description, its markdown page, over any mix of subgroups and Story leaves.',
36
48
  });
37
49
  export const StoryTreeSchema = StoryTreeGroupSchema.annotate({
38
50
  title: 'Story Tree',
@@ -13,21 +13,28 @@ export interface StoryQuestion {
13
13
  }
14
14
  export interface Story {
15
15
  readonly title: string;
16
+ readonly description: string;
17
+ readonly spine: boolean;
16
18
  readonly sourceUrl: string;
17
19
  readonly questions: readonly StoryQuestion[];
18
20
  }
19
21
  export interface StoryGroup {
20
22
  readonly title: string;
23
+ readonly description: string;
21
24
  readonly children: readonly StoryNode[];
22
25
  }
23
26
  export type StoryNode = Story | StoryGroup;
24
27
  export declare const Story: {
25
- make(story: Story): Story;
28
+ make(story: Omit<Story, 'spine'> & {
29
+ readonly spine?: boolean;
30
+ }): Story;
26
31
  question(question: string, options: {
27
32
  readonly answer: string;
28
33
  readonly proof: Effect.Effect<unknown, unknown, StoryContext>;
29
34
  }): StoryQuestion;
30
- group(title: string, children: readonly StoryNode[]): StoryGroup;
35
+ group(title: string, options: {
36
+ readonly description: string;
37
+ }, children: readonly StoryNode[]): StoryGroup;
31
38
  trace<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R | StoryContext>;
32
39
  flow<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R | StoryContext>;
33
40
  assert(description: string, passed: boolean): Effect.Effect<void, never, StoryContext>;
@@ -1 +1 @@
1
- {"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/story/story.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;;2BAK9B,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;qBACvD,CACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,OAAO,KACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;AAP5B,qBAAa,YAAa,SAAQ,iBASf;CAAG;AAEtB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAC;CACzC;AAED,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,UAAU,CAAC;AAE3C,eAAO,MAAM,KAAK;IAChB,IAAI,QAAQ,KAAK,GAAG,KAAK;IAIzB,QAAQ,WACI,MAAM,WACP;QACP,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;KAC/D,GACA,aAAa;IAIhB,KAAK,QAAQ,MAAM,YAAY,SAAS,SAAS,EAAE,GAAG,UAAU;IAIhE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,UACH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;IAcxC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,UACF,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;IAgBxC,MAAM,cACS,MAAM,UACX,OAAO,GACd,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC;CAK5C,CAAC;AAEF,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CActD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAOhE"}
1
+ {"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/story/story.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;;2BAK9B,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;qBACvD,CACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,OAAO,KACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;AAP5B,qBAAa,YAAa,SAAQ,iBASf;CAAG;AAEtB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAC;CACzC;AAED,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,UAAU,CAAC;AAE3C,eAAO,MAAM,KAAK;IAChB,IAAI,QAAQ,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,KAAK;IAIvE,QAAQ,WACI,MAAM,WACP;QACP,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;KAC/D,GACA,aAAa;IAIhB,KAAK,QACI,MAAM,WACJ;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAAE,YAC/B,SAAS,SAAS,EAAE,GAC7B,UAAU;IAIb,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,UACH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;IAcxC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,UACF,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;IAgBxC,MAAM,cACS,MAAM,UACX,OAAO,GACd,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC;CAK5C,CAAC;AAEF,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAgBtD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAWhE"}
@@ -4,13 +4,13 @@ export class StoryContext extends Context.Service()('StoryContext') {
4
4
  }
5
5
  export const Story = {
6
6
  make(story) {
7
- return story;
7
+ return { ...story, spine: story.spine ?? false };
8
8
  },
9
9
  question(question, options) {
10
10
  return { question, answer: options.answer, proof: options.proof };
11
11
  },
12
- group(title, children) {
13
- return { title, children };
12
+ group(title, options, children) {
13
+ return { title, description: options.description, children };
14
14
  },
15
15
  trace(effect) {
16
16
  return Effect.gen(function* () {
@@ -39,6 +39,8 @@ export function isStory(value) {
39
39
  return false;
40
40
  const story = value;
41
41
  return (typeof story.title === 'string' &&
42
+ typeof story.description === 'string' &&
43
+ typeof story.spine === 'boolean' &&
42
44
  typeof story.sourceUrl === 'string' &&
43
45
  Array.isArray(story.questions) &&
44
46
  story.questions.every((question) => typeof question.question === 'string' &&
@@ -49,7 +51,9 @@ export function isStoryGroup(value) {
49
51
  if (typeof value !== 'object' || value === null)
50
52
  return false;
51
53
  const group = value;
52
- if (typeof group.title !== 'string' || !Array.isArray(group.children)) {
54
+ if (typeof group.title !== 'string' ||
55
+ typeof group.description !== 'string' ||
56
+ !Array.isArray(group.children)) {
53
57
  return false;
54
58
  }
55
59
  return group.children.every((child) => isStory(child) || isStoryGroup(child));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laymos",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "description": "Enforces architectural dependency rules and explores source dependencies.",
6
6
  "repository": {
@@ -53,7 +53,7 @@
53
53
  "oxc-resolver": "11.24.2",
54
54
  "tsx": "4.23.12",
55
55
  "yoctocolors": "2.2.0",
56
- "@pkishorez/effect-tracer": "0.0.6"
56
+ "@pkishorez/effect-tracer": "0.0.7"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "26.2.0",