@seanmars/tospec 0.23.0 → 0.26.0-beta
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/CHANGELOG.md +281 -0
- package/README.md +27 -9
- package/assets/claude-mod/tospec-dashboard/.claude-plugin/plugin.json +7 -0
- package/assets/claude-mod/tospec-dashboard/hooks/hooks.json +1 -0
- package/assets/claude-mod/tospec-dashboard/hooks/register.ts +695 -0
- package/assets/claude-mod/tospec-dashboard/tests/register.test.ts +448 -0
- package/assets/claude-mod/tospec-dashboard/types/index.d.ts +8 -0
- package/assets/dashboard/app.js +218 -1180
- package/assets/dashboard/index.html +31 -22
- package/assets/dashboard/lib/charts.js +355 -0
- package/assets/dashboard/lib/derive.js +375 -0
- package/assets/dashboard/lib/dom.js +121 -0
- package/assets/dashboard/lib/routes.js +52 -0
- package/assets/dashboard/lib/store.js +326 -0
- package/assets/dashboard/style.css +3103 -664
- package/assets/dashboard/views/alerts.js +170 -0
- package/assets/dashboard/views/change.js +234 -0
- package/assets/dashboard/views/docs.js +214 -0
- package/assets/dashboard/views/home.js +357 -0
- package/assets/dashboard/views/project.js +168 -0
- package/assets/dashboard/views/session.js +270 -0
- package/assets/dashboard/views/sidebar.js +131 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +116 -102
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +213 -100
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dashboard-relay.d.ts +11 -0
- package/dist/commands/dashboard-relay.d.ts.map +1 -0
- package/dist/commands/dashboard-relay.js +88 -0
- package/dist/commands/dashboard-relay.js.map +1 -0
- package/dist/commands/dashboard.d.ts +69 -100
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +420 -598
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/decision.d.ts +38 -1
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +303 -27
- package/dist/commands/decision.js.map +1 -1
- package/dist/commands/metrics.d.ts +3 -4
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +3 -5
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/shared-output.d.ts +24 -0
- package/dist/commands/shared-output.d.ts.map +1 -1
- package/dist/commands/shared-output.js +21 -2
- package/dist/commands/shared-output.js.map +1 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +12 -9
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.d.ts +19 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +243 -64
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/workflow/instructions.d.ts +7 -1
- package/dist/commands/workflow/instructions.d.ts.map +1 -1
- package/dist/commands/workflow/instructions.js +299 -81
- package/dist/commands/workflow/instructions.js.map +1 -1
- package/dist/commands/workflow/new-change.d.ts.map +1 -1
- package/dist/commands/workflow/new-change.js +26 -17
- package/dist/commands/workflow/new-change.js.map +1 -1
- package/dist/commands/workflow/prompt-escape.d.ts +25 -0
- package/dist/commands/workflow/prompt-escape.d.ts.map +1 -0
- package/dist/commands/workflow/prompt-escape.js +62 -0
- package/dist/commands/workflow/prompt-escape.js.map +1 -0
- package/dist/commands/workflow/schemas.d.ts.map +1 -1
- package/dist/commands/workflow/schemas.js +34 -16
- package/dist/commands/workflow/schemas.js.map +1 -1
- package/dist/commands/workflow/shared.d.ts +46 -10
- package/dist/commands/workflow/shared.d.ts.map +1 -1
- package/dist/commands/workflow/shared.js +66 -30
- package/dist/commands/workflow/shared.js.map +1 -1
- package/dist/commands/workflow/status.d.ts +5 -2
- package/dist/commands/workflow/status.d.ts.map +1 -1
- package/dist/commands/workflow/status.js +62 -28
- package/dist/commands/workflow/status.js.map +1 -1
- package/dist/core/archive.d.ts +38 -0
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +492 -75
- package/dist/core/archive.js.map +1 -1
- package/dist/core/artifact-graph/index.d.ts +2 -2
- package/dist/core/artifact-graph/index.d.ts.map +1 -1
- package/dist/core/artifact-graph/index.js +2 -2
- package/dist/core/artifact-graph/index.js.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +8 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +58 -36
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
- package/dist/core/artifact-graph/resolver.d.ts +39 -12
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
- package/dist/core/artifact-graph/resolver.js +82 -36
- package/dist/core/artifact-graph/resolver.js.map +1 -1
- package/dist/core/artifact-graph/schema.d.ts +24 -0
- package/dist/core/artifact-graph/schema.d.ts.map +1 -1
- package/dist/core/artifact-graph/schema.js +32 -1
- package/dist/core/artifact-graph/schema.js.map +1 -1
- package/dist/core/artifact-graph/stub-detection.d.ts +10 -2
- package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -1
- package/dist/core/artifact-graph/stub-detection.js +18 -11
- package/dist/core/artifact-graph/stub-detection.js.map +1 -1
- package/dist/core/artifact-graph/types.d.ts +2 -2
- package/dist/core/artifact-graph/types.d.ts.map +1 -1
- package/dist/core/artifact-graph/types.js +10 -1
- package/dist/core/artifact-graph/types.js.map +1 -1
- package/dist/core/change-metadata/schema.d.ts.map +1 -1
- package/dist/core/change-metadata/schema.js +18 -4
- package/dist/core/change-metadata/schema.js.map +1 -1
- package/dist/core/change-presenter.d.ts.map +1 -1
- package/dist/core/change-presenter.js +13 -8
- package/dist/core/change-presenter.js.map +1 -1
- package/dist/core/change-status-policy.d.ts.map +1 -1
- package/dist/core/change-status-policy.js +3 -1
- package/dist/core/change-status-policy.js.map +1 -1
- package/dist/core/codex-metrics.d.ts +1 -1
- package/dist/core/codex-metrics.js +1 -1
- package/dist/core/config-schema.d.ts +8 -0
- package/dist/core/config-schema.d.ts.map +1 -1
- package/dist/core/config-schema.js +10 -0
- package/dist/core/config-schema.js.map +1 -1
- package/dist/core/dashboard-activity.d.ts +4 -4
- package/dist/core/dashboard-activity.js +4 -4
- package/dist/core/dashboard-data.d.ts +11 -5
- package/dist/core/dashboard-data.d.ts.map +1 -1
- package/dist/core/dashboard-data.js +54 -28
- package/dist/core/dashboard-data.js.map +1 -1
- package/dist/core/dashboard-hub.d.ts +258 -0
- package/dist/core/dashboard-hub.d.ts.map +1 -0
- package/dist/core/dashboard-hub.js +655 -0
- package/dist/core/dashboard-hub.js.map +1 -0
- package/dist/core/dashboard-routes.d.ts +57 -0
- package/dist/core/dashboard-routes.d.ts.map +1 -0
- package/dist/core/dashboard-routes.js +398 -0
- package/dist/core/dashboard-routes.js.map +1 -0
- package/dist/core/global-config.d.ts +71 -2
- package/dist/core/global-config.d.ts.map +1 -1
- package/dist/core/global-config.js +149 -4
- package/dist/core/global-config.js.map +1 -1
- package/dist/core/id.d.ts +9 -0
- package/dist/core/id.d.ts.map +1 -1
- package/dist/core/id.js +37 -0
- package/dist/core/id.js.map +1 -1
- package/dist/core/init.d.ts +9 -2
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +119 -33
- package/dist/core/init.js.map +1 -1
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +69 -45
- package/dist/core/list.js.map +1 -1
- package/dist/core/local-server.d.ts +10 -3
- package/dist/core/local-server.d.ts.map +1 -1
- package/dist/core/local-server.js +25 -5
- package/dist/core/local-server.js.map +1 -1
- package/dist/core/markdown-render.d.ts.map +1 -1
- package/dist/core/markdown-render.js +7 -2
- package/dist/core/markdown-render.js.map +1 -1
- package/dist/core/migrate.d.ts +24 -2
- package/dist/core/migrate.d.ts.map +1 -1
- package/dist/core/migrate.js +335 -59
- package/dist/core/migrate.js.map +1 -1
- package/dist/core/parsers/change-parser.d.ts +20 -1
- package/dist/core/parsers/change-parser.d.ts.map +1 -1
- package/dist/core/parsers/change-parser.js +76 -73
- package/dist/core/parsers/change-parser.js.map +1 -1
- package/dist/core/parsers/markdown-parser.d.ts +24 -0
- package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
- package/dist/core/parsers/markdown-parser.js +65 -10
- package/dist/core/parsers/markdown-parser.js.map +1 -1
- package/dist/core/parsers/requirement-blocks.d.ts +127 -6
- package/dist/core/parsers/requirement-blocks.d.ts.map +1 -1
- package/dist/core/parsers/requirement-blocks.js +349 -56
- package/dist/core/parsers/requirement-blocks.js.map +1 -1
- package/dist/core/parsers/requirement-text.d.ts +25 -1
- package/dist/core/parsers/requirement-text.d.ts.map +1 -1
- package/dist/core/parsers/requirement-text.js +62 -6
- package/dist/core/parsers/requirement-text.js.map +1 -1
- package/dist/core/parsers/spec-structure.d.ts.map +1 -1
- package/dist/core/parsers/spec-structure.js +36 -24
- package/dist/core/parsers/spec-structure.js.map +1 -1
- package/dist/core/project-config.d.ts +9 -2
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +30 -7
- package/dist/core/project-config.js.map +1 -1
- package/dist/core/project-layout.d.ts +26 -0
- package/dist/core/project-layout.d.ts.map +1 -1
- package/dist/core/project-layout.js +51 -1
- package/dist/core/project-layout.js.map +1 -1
- package/dist/core/schema-names.d.ts +17 -0
- package/dist/core/schema-names.d.ts.map +1 -1
- package/dist/core/schema-names.js +24 -0
- package/dist/core/schema-names.js.map +1 -1
- package/dist/core/shared/dashboard-mod.d.ts +24 -0
- package/dist/core/shared/dashboard-mod.d.ts.map +1 -0
- package/dist/core/shared/dashboard-mod.js +44 -0
- package/dist/core/shared/dashboard-mod.js.map +1 -0
- package/dist/core/shared/index.d.ts +2 -1
- package/dist/core/shared/index.d.ts.map +1 -1
- package/dist/core/shared/index.js +2 -1
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/rules-generation.d.ts +12 -0
- package/dist/core/shared/rules-generation.d.ts.map +1 -1
- package/dist/core/shared/rules-generation.js +26 -2
- package/dist/core/shared/rules-generation.js.map +1 -1
- package/dist/core/shared/skill-generation.d.ts +7 -0
- package/dist/core/shared/skill-generation.d.ts.map +1 -1
- package/dist/core/shared/skill-generation.js +19 -0
- package/dist/core/shared/skill-generation.js.map +1 -1
- package/dist/core/shared/tool-detection.d.ts +10 -1
- package/dist/core/shared/tool-detection.d.ts.map +1 -1
- package/dist/core/shared/tool-detection.js +37 -6
- package/dist/core/shared/tool-detection.js.map +1 -1
- package/dist/core/spec-presenter.d.ts.map +1 -1
- package/dist/core/spec-presenter.js +23 -1
- package/dist/core/spec-presenter.js.map +1 -1
- package/dist/core/specs-apply.d.ts +98 -1
- package/dist/core/specs-apply.d.ts.map +1 -1
- package/dist/core/specs-apply.js +426 -63
- package/dist/core/specs-apply.js.map +1 -1
- package/dist/core/templates/fragments/change-diff.d.ts +37 -0
- package/dist/core/templates/fragments/change-diff.d.ts.map +1 -0
- package/dist/core/templates/fragments/change-diff.js +37 -0
- package/dist/core/templates/fragments/change-diff.js.map +1 -0
- package/dist/core/templates/fragments/sync-fail.d.ts +8 -0
- package/dist/core/templates/fragments/sync-fail.d.ts.map +1 -0
- package/dist/core/templates/fragments/sync-fail.js +8 -0
- package/dist/core/templates/fragments/sync-fail.js.map +1 -0
- package/dist/core/templates/fragments/validate.d.ts +14 -1
- package/dist/core/templates/fragments/validate.d.ts.map +1 -1
- package/dist/core/templates/fragments/validate.js +14 -1
- package/dist/core/templates/fragments/validate.js.map +1 -1
- package/dist/core/templates/fragments/verify.d.ts +2 -2
- package/dist/core/templates/fragments/verify.d.ts.map +1 -1
- package/dist/core/templates/fragments/verify.js +8 -5
- package/dist/core/templates/fragments/verify.js.map +1 -1
- package/dist/core/templates/workflows/apply.d.ts +1 -1
- package/dist/core/templates/workflows/apply.d.ts.map +1 -1
- package/dist/core/templates/workflows/apply.js +29 -10
- package/dist/core/templates/workflows/apply.js.map +1 -1
- package/dist/core/templates/workflows/archive.d.ts.map +1 -1
- package/dist/core/templates/workflows/archive.js +27 -9
- package/dist/core/templates/workflows/archive.js.map +1 -1
- package/dist/core/templates/workflows/decision.d.ts.map +1 -1
- package/dist/core/templates/workflows/decision.js +10 -4
- package/dist/core/templates/workflows/decision.js.map +1 -1
- package/dist/core/templates/workflows/explore.d.ts +2 -2
- package/dist/core/templates/workflows/explore.d.ts.map +1 -1
- package/dist/core/templates/workflows/explore.js +7 -3
- package/dist/core/templates/workflows/explore.js.map +1 -1
- package/dist/core/templates/workflows/grill.d.ts +2 -2
- package/dist/core/templates/workflows/grill.d.ts.map +1 -1
- package/dist/core/templates/workflows/grill.js +5 -4
- package/dist/core/templates/workflows/grill.js.map +1 -1
- package/dist/core/templates/workflows/issue.d.ts.map +1 -1
- package/dist/core/templates/workflows/issue.js +9 -3
- package/dist/core/templates/workflows/issue.js.map +1 -1
- package/dist/core/templates/workflows/propose.d.ts.map +1 -1
- package/dist/core/templates/workflows/propose.js +4 -1
- package/dist/core/templates/workflows/propose.js.map +1 -1
- package/dist/core/templates/workflows/sync.d.ts.map +1 -1
- package/dist/core/templates/workflows/sync.js +25 -11
- package/dist/core/templates/workflows/sync.js.map +1 -1
- package/dist/core/templates/workflows/update.d.ts.map +1 -1
- package/dist/core/templates/workflows/update.js +17 -10
- package/dist/core/templates/workflows/update.js.map +1 -1
- package/dist/core/update.d.ts +5 -0
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +69 -29
- package/dist/core/update.js.map +1 -1
- package/dist/core/validation/constants.d.ts +7 -1
- package/dist/core/validation/constants.d.ts.map +1 -1
- package/dist/core/validation/constants.js +16 -2
- package/dist/core/validation/constants.js.map +1 -1
- package/dist/core/validation/purpose-placeholder.d.ts.map +1 -1
- package/dist/core/validation/purpose-placeholder.js +35 -11
- package/dist/core/validation/purpose-placeholder.js.map +1 -1
- package/dist/core/validation/section-validator.d.ts +7 -0
- package/dist/core/validation/section-validator.d.ts.map +1 -1
- package/dist/core/validation/section-validator.js +22 -2
- package/dist/core/validation/section-validator.js.map +1 -1
- package/dist/core/validation/sister-changes.d.ts +93 -0
- package/dist/core/validation/sister-changes.d.ts.map +1 -0
- package/dist/core/validation/sister-changes.js +180 -0
- package/dist/core/validation/sister-changes.js.map +1 -0
- package/dist/core/validation/task-markers.d.ts +9 -0
- package/dist/core/validation/task-markers.d.ts.map +1 -0
- package/dist/core/validation/task-markers.js +71 -0
- package/dist/core/validation/task-markers.js.map +1 -0
- package/dist/core/validation/types.d.ts +58 -0
- package/dist/core/validation/types.d.ts.map +1 -1
- package/dist/core/validation/types.js +22 -0
- package/dist/core/validation/types.js.map +1 -1
- package/dist/core/validation/validator.d.ts +79 -2
- package/dist/core/validation/validator.d.ts.map +1 -1
- package/dist/core/validation/validator.js +565 -84
- package/dist/core/validation/validator.js.map +1 -1
- package/dist/utils/change-metadata.d.ts +62 -5
- package/dist/utils/change-metadata.d.ts.map +1 -1
- package/dist/utils/change-metadata.js +193 -31
- package/dist/utils/change-metadata.js.map +1 -1
- package/dist/utils/change-utils.d.ts +21 -0
- package/dist/utils/change-utils.d.ts.map +1 -1
- package/dist/utils/change-utils.js +168 -53
- package/dist/utils/change-utils.js.map +1 -1
- package/dist/utils/command-line.d.ts +11 -0
- package/dist/utils/command-line.d.ts.map +1 -0
- package/dist/utils/command-line.js +43 -0
- package/dist/utils/command-line.js.map +1 -0
- package/dist/utils/display-width.d.ts +10 -0
- package/dist/utils/display-width.d.ts.map +1 -0
- package/dist/utils/display-width.js +15 -0
- package/dist/utils/display-width.js.map +1 -0
- package/dist/utils/file-system.d.ts +16 -0
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +49 -2
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +11 -5
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/interactive.d.ts.map +1 -1
- package/dist/utils/interactive.js +3 -1
- package/dist/utils/interactive.js.map +1 -1
- package/dist/utils/item-discovery.d.ts +3 -2
- package/dist/utils/item-discovery.d.ts.map +1 -1
- package/dist/utils/item-discovery.js +34 -16
- package/dist/utils/item-discovery.js.map +1 -1
- package/dist/utils/link.d.ts +0 -1
- package/dist/utils/link.d.ts.map +1 -1
- package/dist/utils/link.js +0 -1
- package/dist/utils/link.js.map +1 -1
- package/dist/utils/match.d.ts.map +1 -1
- package/dist/utils/match.js +10 -1
- package/dist/utils/match.js.map +1 -1
- package/dist/utils/option-flags.d.ts +9 -0
- package/dist/utils/option-flags.d.ts.map +1 -0
- package/dist/utils/option-flags.js +12 -0
- package/dist/utils/option-flags.js.map +1 -0
- package/dist/utils/sentence.d.ts +9 -0
- package/dist/utils/sentence.d.ts.map +1 -0
- package/dist/utils/sentence.js +12 -0
- package/dist/utils/sentence.js.map +1 -0
- package/dist/utils/spec-files.d.ts +84 -1
- package/dist/utils/spec-files.d.ts.map +1 -1
- package/dist/utils/spec-files.js +192 -3
- package/dist/utils/spec-files.js.map +1 -1
- package/dist/utils/task-progress.d.ts.map +1 -1
- package/dist/utils/task-progress.js +32 -9
- package/dist/utils/task-progress.js.map +1 -1
- package/dist/utils/timestamp.d.ts +27 -0
- package/dist/utils/timestamp.d.ts.map +1 -1
- package/dist/utils/timestamp.js +37 -0
- package/dist/utils/timestamp.js.map +1 -1
- package/package.json +6 -2
- package/schemas/decision/templates/decision.md +2 -2
- package/schemas/issue/schema.yaml +12 -6
- package/schemas/issue/templates/task.md +1 -1
- package/schemas/sdd/schema.yaml +21 -10
- package/schemas/sdd/templates/proposal.md +1 -1
- package/assets/dashboard/chart.umd.js +0 -14
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type DeltaPlan } from '../parsers/requirement-blocks.js';
|
|
2
|
+
/** One capability's delta in another in-progress change. */
|
|
3
|
+
export interface SisterChangeDelta {
|
|
4
|
+
/** The change's directory name under `tospec/changes/`. */
|
|
5
|
+
change: string;
|
|
6
|
+
/** The capability folder name, as that change's delta spells it. */
|
|
7
|
+
capability: string;
|
|
8
|
+
plan: DeltaPlan;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The parsed delta specs of every in-progress change except `changeName`.
|
|
12
|
+
* `changesDir` is `<root>/tospec/changes`; `archive/` is never listed.
|
|
13
|
+
*
|
|
14
|
+
* Validate reads one change against `tospec/specs/` only, so a delta written
|
|
15
|
+
* against a capability another change is still adding, or a requirement it is
|
|
16
|
+
* still rewriting, cannot be told from a plain mistake. This is the read that
|
|
17
|
+
* lets a finding name the other change.
|
|
18
|
+
*
|
|
19
|
+
* Best effort: a change or delta that cannot be read is left out, since the
|
|
20
|
+
* callers only add a hint to a finding that stands without it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function listSisterChangeDeltas(changesDir: string, changeName: string): Promise<SisterChangeDelta[]>;
|
|
23
|
+
export type DeltaOperation = 'ADDED' | 'MODIFIED' | 'REMOVED' | 'RENAMED';
|
|
24
|
+
/** One requirement a delta acts on. */
|
|
25
|
+
export interface DeltaTouch {
|
|
26
|
+
operation: DeltaOperation;
|
|
27
|
+
/** As the delta spells it; a RENAMED by its FROM name. */
|
|
28
|
+
requirement: string;
|
|
29
|
+
/** 1-based line of the entry (a RENAMED's FROM: line), where the caller has one. */
|
|
30
|
+
line?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The requirements `plan` acts on through `operations`. A RENAMED counts by
|
|
34
|
+
* its FROM name: that is the requirement the main spec holds, and the one a
|
|
35
|
+
* second change's block would have been copied from.
|
|
36
|
+
*/
|
|
37
|
+
export declare function listDeltaTouches(plan: DeltaPlan, operations: readonly DeltaOperation[]): DeltaTouch[];
|
|
38
|
+
/** A requirement this change's delta and another in-progress change's both act on. */
|
|
39
|
+
export interface SharedRequirement {
|
|
40
|
+
/** The other change. */
|
|
41
|
+
change: string;
|
|
42
|
+
own: DeltaTouch;
|
|
43
|
+
sister: DeltaTouch;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pairs each of `own` (the touches of one capability's delta) with every
|
|
47
|
+
* sister delta on the same capability that acts on the same requirement
|
|
48
|
+
* through `sisterOperations`.
|
|
49
|
+
*
|
|
50
|
+
* Names are compared folded, the rule the duplicate checks use: two changes
|
|
51
|
+
* spelling one requirement `Input validation` and `Input Validation` are
|
|
52
|
+
* still about one requirement, and this only ever adds a WARNING.
|
|
53
|
+
*/
|
|
54
|
+
export declare function findSharedRequirements(capability: string, own: readonly DeltaTouch[], sisters: readonly SisterChangeDelta[], sisterOperations: readonly DeltaOperation[]): SharedRequirement[];
|
|
55
|
+
/** A rewrite whose requirement a sister change ADDs under another spelling. */
|
|
56
|
+
export interface HeaderMismatch {
|
|
57
|
+
/** The sister change. */
|
|
58
|
+
change: string;
|
|
59
|
+
own: DeltaTouch;
|
|
60
|
+
/** The name as the sister's ADDED spells it, the one the merge will hold. */
|
|
61
|
+
suggestedHeader: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The touches in `own` (MODIFIED, REMOVED, RENAMED) whose name folds onto a
|
|
65
|
+
* sister's ADDED name on the same capability without matching it exactly.
|
|
66
|
+
* The scan above matches folded, the merge exactly, so archiving the sister
|
|
67
|
+
* first, which the folded match implies, leaves this delta refused all the same.
|
|
68
|
+
*
|
|
69
|
+
* Skipped when the main spec already holds the name, exactly or folded: the
|
|
70
|
+
* merge matches against it, not the sister, and a near miss there has its
|
|
71
|
+
* own refusal. Also skipped when another sister ADDs the name exactly: that
|
|
72
|
+
* one is the requirement the delta was written against.
|
|
73
|
+
*/
|
|
74
|
+
export declare function findHeaderMismatches(capability: string, own: readonly DeltaTouch[], sisters: readonly SisterChangeDelta[], mainSpecNames: readonly string[]): HeaderMismatch[];
|
|
75
|
+
/** When a file was created, or undefined when the platform cannot say. */
|
|
76
|
+
export type BirthtimeSource = (file: string) => Promise<Date | undefined>;
|
|
77
|
+
/**
|
|
78
|
+
* When the change at `changeDir` was created, in epoch milliseconds, or
|
|
79
|
+
* undefined when that can no longer be told. Two changes rewriting one
|
|
80
|
+
* requirement must agree on which archives first, and the older one is the
|
|
81
|
+
* only order both can read without a state store.
|
|
82
|
+
*
|
|
83
|
+
* A datetime `created` is the answer. A date-only one (every change made
|
|
84
|
+
* before `new change` wrote the time) is completed by the birthtime of
|
|
85
|
+
* `.tospec.yaml`, which `new change` wrote that day; but clone, checkout and
|
|
86
|
+
* copy all reset birthtime, and a platform without one reports the epoch, so
|
|
87
|
+
* it counts only when its local date is the `created` date. Anything less is
|
|
88
|
+
* undefined: a guessed order would tell one side to wait for the other with
|
|
89
|
+
* no ground for it. `birthtimeOf` is injectable because birthtime cannot be
|
|
90
|
+
* set on every platform, so tests cannot produce it on disk.
|
|
91
|
+
*/
|
|
92
|
+
export declare function readChangeOrderKey(changeDir: string, birthtimeOf?: BirthtimeSource): Promise<number | undefined>;
|
|
93
|
+
//# sourceMappingURL=sister-changes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sister-changes.d.ts","sourceRoot":"","sources":["../../../src/core/validation/sister-changes.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,kCAAkC,CAAC;AAE1C,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,SAAS,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA4B9B;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE1E,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,cAAc,CAAC;IAC1B,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,UAAU,EAAE,CAerG;AAED,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,SAAS,UAAU,EAAE,EAC1B,OAAO,EAAE,SAAS,iBAAiB,EAAE,EACrC,gBAAgB,EAAE,SAAS,cAAc,EAAE,GAC1C,iBAAiB,EAAE,CAcrB;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,UAAU,CAAC;IAChB,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,SAAS,UAAU,EAAE,EAC1B,OAAO,EAAE,SAAS,iBAAiB,EAAE,EACrC,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,cAAc,EAAE,CAgBlB;AAED,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC;AAU1E;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,WAAW,GAAE,eAA+B,GAC3C,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAe7B"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { listActiveChangeDirs } from '../../utils/item-discovery.js';
|
|
5
|
+
import { formatLocalDate, parseCreated } from '../../utils/timestamp.js';
|
|
6
|
+
import { METADATA_FILENAME } from '../../utils/change-metadata.js';
|
|
7
|
+
import { findDeltaSpecSources } from '../specs-apply.js';
|
|
8
|
+
import { findFoldedMatch, foldRequirementName, normalizeRequirementName, parseDeltaSpec, } from '../parsers/requirement-blocks.js';
|
|
9
|
+
/**
|
|
10
|
+
* The parsed delta specs of every in-progress change except `changeName`.
|
|
11
|
+
* `changesDir` is `<root>/tospec/changes`; `archive/` is never listed.
|
|
12
|
+
*
|
|
13
|
+
* Validate reads one change against `tospec/specs/` only, so a delta written
|
|
14
|
+
* against a capability another change is still adding, or a requirement it is
|
|
15
|
+
* still rewriting, cannot be told from a plain mistake. This is the read that
|
|
16
|
+
* lets a finding name the other change.
|
|
17
|
+
*
|
|
18
|
+
* Best effort: a change or delta that cannot be read is left out, since the
|
|
19
|
+
* callers only add a hint to a finding that stands without it.
|
|
20
|
+
*/
|
|
21
|
+
export async function listSisterChangeDeltas(changesDir, changeName) {
|
|
22
|
+
let changes;
|
|
23
|
+
try {
|
|
24
|
+
changes = await listActiveChangeDirs(changesDir);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const deltas = [];
|
|
30
|
+
for (const change of changes) {
|
|
31
|
+
if (change === changeName)
|
|
32
|
+
continue;
|
|
33
|
+
let sources;
|
|
34
|
+
try {
|
|
35
|
+
// The merge's own list, so a misplaced file archive would never read
|
|
36
|
+
// cannot be named as the change that adds a capability.
|
|
37
|
+
sources = await findDeltaSpecSources(path.join(changesDir, change, 'specs'));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
for (const { capability, source } of sources) {
|
|
43
|
+
try {
|
|
44
|
+
deltas.push({ change, capability, plan: parseDeltaSpec(await fs.readFile(source, 'utf-8')) });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return deltas;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The requirements `plan` acts on through `operations`. A RENAMED counts by
|
|
55
|
+
* its FROM name: that is the requirement the main spec holds, and the one a
|
|
56
|
+
* second change's block would have been copied from.
|
|
57
|
+
*/
|
|
58
|
+
export function listDeltaTouches(plan, operations) {
|
|
59
|
+
const touches = [];
|
|
60
|
+
if (operations.includes('ADDED')) {
|
|
61
|
+
for (const block of plan.added)
|
|
62
|
+
touches.push({ operation: 'ADDED', requirement: block.name, line: block.startLine });
|
|
63
|
+
}
|
|
64
|
+
if (operations.includes('MODIFIED')) {
|
|
65
|
+
for (const block of plan.modified)
|
|
66
|
+
touches.push({ operation: 'MODIFIED', requirement: block.name, line: block.startLine });
|
|
67
|
+
}
|
|
68
|
+
if (operations.includes('REMOVED')) {
|
|
69
|
+
for (const entry of plan.removedEntries)
|
|
70
|
+
touches.push({ operation: 'REMOVED', requirement: entry.name, line: entry.startLine });
|
|
71
|
+
}
|
|
72
|
+
if (operations.includes('RENAMED')) {
|
|
73
|
+
for (const { from, fromLine } of plan.renamedEntries)
|
|
74
|
+
touches.push({ operation: 'RENAMED', requirement: from, line: fromLine });
|
|
75
|
+
}
|
|
76
|
+
return touches;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Pairs each of `own` (the touches of one capability's delta) with every
|
|
80
|
+
* sister delta on the same capability that acts on the same requirement
|
|
81
|
+
* through `sisterOperations`.
|
|
82
|
+
*
|
|
83
|
+
* Names are compared folded, the rule the duplicate checks use: two changes
|
|
84
|
+
* spelling one requirement `Input validation` and `Input Validation` are
|
|
85
|
+
* still about one requirement, and this only ever adds a WARNING.
|
|
86
|
+
*/
|
|
87
|
+
export function findSharedRequirements(capability, own, sisters, sisterOperations) {
|
|
88
|
+
const shared = [];
|
|
89
|
+
for (const sisterDelta of sisters) {
|
|
90
|
+
if (sisterDelta.capability !== capability)
|
|
91
|
+
continue;
|
|
92
|
+
const sisterTouches = listDeltaTouches(sisterDelta.plan, sisterOperations);
|
|
93
|
+
for (const ownTouch of own) {
|
|
94
|
+
const folded = foldRequirementName(ownTouch.requirement);
|
|
95
|
+
for (const sisterTouch of sisterTouches) {
|
|
96
|
+
if (foldRequirementName(sisterTouch.requirement) !== folded)
|
|
97
|
+
continue;
|
|
98
|
+
shared.push({ change: sisterDelta.change, own: ownTouch, sister: sisterTouch });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return shared;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The touches in `own` (MODIFIED, REMOVED, RENAMED) whose name folds onto a
|
|
106
|
+
* sister's ADDED name on the same capability without matching it exactly.
|
|
107
|
+
* The scan above matches folded, the merge exactly, so archiving the sister
|
|
108
|
+
* first, which the folded match implies, leaves this delta refused all the same.
|
|
109
|
+
*
|
|
110
|
+
* Skipped when the main spec already holds the name, exactly or folded: the
|
|
111
|
+
* merge matches against it, not the sister, and a near miss there has its
|
|
112
|
+
* own refusal. Also skipped when another sister ADDs the name exactly: that
|
|
113
|
+
* one is the requirement the delta was written against.
|
|
114
|
+
*/
|
|
115
|
+
export function findHeaderMismatches(capability, own, sisters, mainSpecNames) {
|
|
116
|
+
const adders = sisters.filter((s) => s.capability === capability);
|
|
117
|
+
const mismatches = [];
|
|
118
|
+
for (const ownTouch of own) {
|
|
119
|
+
if (ownTouch.operation === 'ADDED')
|
|
120
|
+
continue;
|
|
121
|
+
if (findFoldedMatch(mainSpecNames, ownTouch.requirement) !== undefined)
|
|
122
|
+
continue;
|
|
123
|
+
const exact = normalizeRequirementName(ownTouch.requirement);
|
|
124
|
+
const addedNames = adders.flatMap((s) => s.plan.added.map((block) => ({ change: s.change, name: block.name })));
|
|
125
|
+
if (addedNames.some(({ name }) => normalizeRequirementName(name) === exact))
|
|
126
|
+
continue;
|
|
127
|
+
const folded = foldRequirementName(ownTouch.requirement);
|
|
128
|
+
for (const { change, name } of addedNames) {
|
|
129
|
+
if (foldRequirementName(name) !== folded)
|
|
130
|
+
continue;
|
|
131
|
+
mismatches.push({ change, own: ownTouch, suggestedHeader: normalizeRequirementName(name) });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return mismatches;
|
|
135
|
+
}
|
|
136
|
+
const statBirthtime = async (file) => {
|
|
137
|
+
try {
|
|
138
|
+
return (await fs.stat(file)).birthtime;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* When the change at `changeDir` was created, in epoch milliseconds, or
|
|
146
|
+
* undefined when that can no longer be told. Two changes rewriting one
|
|
147
|
+
* requirement must agree on which archives first, and the older one is the
|
|
148
|
+
* only order both can read without a state store.
|
|
149
|
+
*
|
|
150
|
+
* A datetime `created` is the answer. A date-only one (every change made
|
|
151
|
+
* before `new change` wrote the time) is completed by the birthtime of
|
|
152
|
+
* `.tospec.yaml`, which `new change` wrote that day; but clone, checkout and
|
|
153
|
+
* copy all reset birthtime, and a platform without one reports the epoch, so
|
|
154
|
+
* it counts only when its local date is the `created` date. Anything less is
|
|
155
|
+
* undefined: a guessed order would tell one side to wait for the other with
|
|
156
|
+
* no ground for it. `birthtimeOf` is injectable because birthtime cannot be
|
|
157
|
+
* set on every platform, so tests cannot produce it on disk.
|
|
158
|
+
*/
|
|
159
|
+
export async function readChangeOrderKey(changeDir, birthtimeOf = statBirthtime) {
|
|
160
|
+
const metadataPath = path.join(changeDir, METADATA_FILENAME);
|
|
161
|
+
let created;
|
|
162
|
+
try {
|
|
163
|
+
created = parseYaml(await fs.readFile(metadataPath, 'utf-8'))?.created;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
if (typeof created !== 'string')
|
|
169
|
+
return undefined;
|
|
170
|
+
const parsed = parseCreated(created);
|
|
171
|
+
if (parsed === null)
|
|
172
|
+
return undefined;
|
|
173
|
+
if (parsed.hasTime)
|
|
174
|
+
return parsed.date.getTime();
|
|
175
|
+
const birthtime = await birthtimeOf(metadataPath);
|
|
176
|
+
if (birthtime === undefined || Number.isNaN(birthtime.getTime()))
|
|
177
|
+
return undefined;
|
|
178
|
+
return formatLocalDate(birthtime) === created ? birthtime.getTime() : undefined;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=sister-changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sister-changes.js","sourceRoot":"","sources":["../../../src/core/validation/sister-changes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TaskNumberingDocument } from './task-numbering.js';
|
|
2
|
+
export interface TaskMarkerIssue {
|
|
3
|
+
path: string;
|
|
4
|
+
/** Absent for the file-level "no tasks" finding. */
|
|
5
|
+
line?: number;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function findTaskMarkerIssues(documents: readonly TaskNumberingDocument[]): TaskMarkerIssue[];
|
|
9
|
+
//# sourceMappingURL=task-markers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-markers.d.ts","sourceRoot":"","sources":["../../../src/core/validation/task-markers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAgCjE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,SAAS,qBAAqB,EAAE,GAC1C,eAAe,EAAE,CAyCnB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { parseTasksFromContent } from '../../utils/task-progress.js';
|
|
2
|
+
import { buildCodeFenceMask, normalizeDocument } from '../parsers/requirement-text.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tracked-task lines the counter cannot see.
|
|
5
|
+
*
|
|
6
|
+
* The counter follows GFM on purpose (see `TASK_PATTERN`): a box GitHub does
|
|
7
|
+
* not draw is not a task. The cost is that `- [~] 1.2 deferred` drops out of the
|
|
8
|
+
* denominator, so a change with that line left reads `all_done` and archives
|
|
9
|
+
* with no gate firing. Widening the counter would count boxes nobody can tick
|
|
10
|
+
* in the rendered file, so validate names the line instead.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* A list item opening with a short bracket. Kept narrow so prose does not trip
|
|
14
|
+
* it: at most two characters inside (`[~]`, `[]`, `[ x]`), and the `]` not
|
|
15
|
+
* followed by `(`, `[` or `:`, which would make it a link, a reference link or
|
|
16
|
+
* a link definition. A leading `^` is a footnote reference.
|
|
17
|
+
*
|
|
18
|
+
* Linear by construction: anchored, `[ \t]*` and the list marker share no
|
|
19
|
+
* character, and the bracket body is bounded.
|
|
20
|
+
*/
|
|
21
|
+
const CHECKBOX_LIKE = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+\[(?!\^)([^\]\r\n]{0,2})\](?![([:])/;
|
|
22
|
+
/**
|
|
23
|
+
* A letter or digit other than x reads as a label (`[P1]`, `[v2]`, `[a]`), not
|
|
24
|
+
* a mistyped box. The length bound alone let those through, and under
|
|
25
|
+
* `--strict` the warning refused a change over a priority tag. `[v]` goes with
|
|
26
|
+
* them: a check-mark habit for some, but telling it from a one-letter label is
|
|
27
|
+
* a guess, and a missed warning costs less than a refused change.
|
|
28
|
+
*/
|
|
29
|
+
const LABEL_CHARACTER = /[0-9a-wyz]/i;
|
|
30
|
+
export function findTaskMarkerIssues(documents) {
|
|
31
|
+
const issues = [];
|
|
32
|
+
let taskCount = 0;
|
|
33
|
+
const nonBlankPaths = [];
|
|
34
|
+
for (const document of documents) {
|
|
35
|
+
// Split and masked exactly as parseTasksFromContent does, so the line
|
|
36
|
+
// numbers reported here are the ones the counter uses.
|
|
37
|
+
const lines = normalizeDocument(document.content).split('\n');
|
|
38
|
+
const fenced = buildCodeFenceMask(lines);
|
|
39
|
+
const taskLines = new Set(parseTasksFromContent(document.content).map((t) => t.line));
|
|
40
|
+
taskCount += taskLines.size;
|
|
41
|
+
if (document.content.trim() !== '')
|
|
42
|
+
nonBlankPaths.push(document.path);
|
|
43
|
+
lines.forEach((line, index) => {
|
|
44
|
+
if (fenced[index] || taskLines.has(index + 1))
|
|
45
|
+
return;
|
|
46
|
+
const match = CHECKBOX_LIKE.exec(line);
|
|
47
|
+
if (!match || LABEL_CHARACTER.test(match[1]))
|
|
48
|
+
return;
|
|
49
|
+
const marker = `[${match[1]}]`;
|
|
50
|
+
issues.push({
|
|
51
|
+
path: document.path,
|
|
52
|
+
line: index + 1,
|
|
53
|
+
message: /^[ xX]$/.test(match[1])
|
|
54
|
+
? `"${marker}" has no blank after it, so GitHub does not draw a checkbox and the line is not counted as a task. Add a space after "${marker}".`
|
|
55
|
+
: `"${marker}" is not a checkbox GitHub draws, so the line is not counted as a task. Use "[ ]" for open work or "[x]" for done work.`,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Judged across all tracked files, like apply's "contains no tasks" state: in
|
|
60
|
+
// a multi-file schema one file without tasks (an index, say) is legitimate.
|
|
61
|
+
if (taskCount === 0) {
|
|
62
|
+
for (const filePath of nonBlankPaths) {
|
|
63
|
+
issues.push({
|
|
64
|
+
path: filePath,
|
|
65
|
+
message: `${filePath} has content but no tasks, so apply reports it blocked and archive cannot count progress. Write each task as "- [ ] <id> <text>".`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return issues;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=task-markers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-markers.js","sourceRoot":"","sources":["../../../src/core/validation/task-markers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGvF;;;;;;;;GAQG;AAEH;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAG,uEAAuE,CAAC;AAE9F;;;;;;GAMG;AACH,MAAM,eAAe,GAAG,aAAa,CAAC;AAStC,MAAM,UAAU,oBAAoB,CAClC,SAA2C;IAE3C,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,aAAa,GAAa,EAAE,CAAC;IAEnC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,sEAAsE;QACtE,uDAAuD;QACvD,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,qBAAqB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACtF,SAAS,IAAI,SAAS,CAAC,IAAI,CAAC;QAC5B,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEtE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC;gBAAE,OAAO;YACtD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAE,OAAO;YACrD,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,IAAI,EAAE,KAAK,GAAG,CAAC;gBACf,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAC/B,CAAC,CAAC,IAAI,MAAM,yHAAyH,MAAM,IAAI;oBAC/I,CAAC,CAAC,IAAI,MAAM,yHAAyH;aACxI,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,4EAA4E;IAC5E,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;QACpB,KAAK,MAAM,QAAQ,IAAI,aAAa,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,GAAG,QAAQ,mIAAmI;aACxJ,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -15,7 +15,51 @@ export interface ValidationIssue {
|
|
|
15
15
|
* `ValidationReport.notes`, deduplicated, and clears them from the issues.
|
|
16
16
|
*/
|
|
17
17
|
note?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Set only on findings a caller has to act on without reading the message
|
|
20
|
+
* (`ARCHIVE_REFUSAL_CODE`, `SISTER_CHANGE_OVERLAP_CODE`,
|
|
21
|
+
* `REQUIREMENT_HEADER_MISMATCH_CODE`). Most issues have
|
|
22
|
+
* none: their level and path say enough.
|
|
23
|
+
*/
|
|
24
|
+
code?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The other in-progress changes that must archive before this one, when the
|
|
27
|
+
* archive order is the whole cause: no edit to this delta settles it, and
|
|
28
|
+
* an agent told to clear every WARNING would otherwise "fix" a correct
|
|
29
|
+
* delta. Set only where the message itself says to archive them first.
|
|
30
|
+
*/
|
|
31
|
+
blockedBy?: string[];
|
|
32
|
+
/**
|
|
33
|
+
* The exact requirement name to write instead, on a delta name that differs
|
|
34
|
+
* only in case or spacing from one another in-progress change ADDs
|
|
35
|
+
* (`REQUIREMENT_HEADER_MISMATCH_CODE`, and the refusal it causes) or from
|
|
36
|
+
* one the main spec holds (that refusal alone). A field, so the name can be
|
|
37
|
+
* fixed without parsing the message.
|
|
38
|
+
*/
|
|
39
|
+
suggestedHeader?: string;
|
|
18
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* A delta archive's merge would refuse. A WARNING, not an ERROR: when a sister
|
|
43
|
+
* change still has to add the capability, this change is not wrong, only
|
|
44
|
+
* early. So `valid` stays true and an agent reading only the verdict or the
|
|
45
|
+
* exit code missed it; matching the message text breaks on any rewording.
|
|
46
|
+
*/
|
|
47
|
+
export declare const ARCHIVE_REFUSAL_CODE = "archive_would_refuse";
|
|
48
|
+
/**
|
|
49
|
+
* This delta rewrites a requirement another in-progress change also acts on,
|
|
50
|
+
* so whichever archives second overwrites the first one's edit. A WARNING
|
|
51
|
+
* coded for the same reason as a refusal: what to do turns on the order, and
|
|
52
|
+
* the order is in `blockedBy`, not in the wording.
|
|
53
|
+
*/
|
|
54
|
+
export declare const SISTER_CHANGE_OVERLAP_CODE = "sister_change_overlap";
|
|
55
|
+
/**
|
|
56
|
+
* A MODIFIED, REMOVED or RENAMED (FROM) name that folds onto a sister
|
|
57
|
+
* change's ADDED name but is not spelled the same. The sister scan matches
|
|
58
|
+
* folded and the merge matches exactly, so "archive the sister first" would
|
|
59
|
+
* promise a fix the merge then refuses. An ERROR, unlike the refusal: no
|
|
60
|
+
* archive order makes the delta apply, only the edit `suggestedHeader` names.
|
|
61
|
+
*/
|
|
62
|
+
export declare const REQUIREMENT_HEADER_MISMATCH_CODE = "requirement_header_mismatch";
|
|
19
63
|
export interface ValidationReport {
|
|
20
64
|
valid: boolean;
|
|
21
65
|
issues: ValidationIssue[];
|
|
@@ -27,6 +71,20 @@ export interface ValidationReport {
|
|
|
27
71
|
/** Distinct `ValidationIssue.note` values, each carried once. */
|
|
28
72
|
notes?: string[];
|
|
29
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Why archive would refuse a rebuilt spec. `main`: the main spec already
|
|
76
|
+
* fails, so `issues` are its own and `specPath` is the file to fix. `delta`:
|
|
77
|
+
* the main spec is valid, so the change's delta caused the rebuilt spec's
|
|
78
|
+
* `issues`.
|
|
79
|
+
*/
|
|
80
|
+
export type RebuiltSpecFailure = {
|
|
81
|
+
source: 'main';
|
|
82
|
+
specPath: string;
|
|
83
|
+
issues: ValidationIssue[];
|
|
84
|
+
} | {
|
|
85
|
+
source: 'delta';
|
|
86
|
+
issues: ValidationIssue[];
|
|
87
|
+
};
|
|
30
88
|
/**
|
|
31
89
|
* Severity symbol for human output. Shared so no two surfaces show different
|
|
32
90
|
* glyphs for one level; the line formats around it differ on purpose
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,gCAAgC,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,GAC/D;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,MAAM,CAInE"}
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A delta archive's merge would refuse. A WARNING, not an ERROR: when a sister
|
|
3
|
+
* change still has to add the capability, this change is not wrong, only
|
|
4
|
+
* early. So `valid` stays true and an agent reading only the verdict or the
|
|
5
|
+
* exit code missed it; matching the message text breaks on any rewording.
|
|
6
|
+
*/
|
|
7
|
+
export const ARCHIVE_REFUSAL_CODE = 'archive_would_refuse';
|
|
8
|
+
/**
|
|
9
|
+
* This delta rewrites a requirement another in-progress change also acts on,
|
|
10
|
+
* so whichever archives second overwrites the first one's edit. A WARNING
|
|
11
|
+
* coded for the same reason as a refusal: what to do turns on the order, and
|
|
12
|
+
* the order is in `blockedBy`, not in the wording.
|
|
13
|
+
*/
|
|
14
|
+
export const SISTER_CHANGE_OVERLAP_CODE = 'sister_change_overlap';
|
|
15
|
+
/**
|
|
16
|
+
* A MODIFIED, REMOVED or RENAMED (FROM) name that folds onto a sister
|
|
17
|
+
* change's ADDED name but is not spelled the same. The sister scan matches
|
|
18
|
+
* folded and the merge matches exactly, so "archive the sister first" would
|
|
19
|
+
* promise a fix the merge then refuses. An ERROR, unlike the refusal: no
|
|
20
|
+
* archive order makes the delta apply, only the edit `suggestedHeader` names.
|
|
21
|
+
*/
|
|
22
|
+
export const REQUIREMENT_HEADER_MISMATCH_CODE = 'requirement_header_mismatch';
|
|
1
23
|
/**
|
|
2
24
|
* Severity symbol for human output. Shared so no two surfaces show different
|
|
3
25
|
* glyphs for one level; the line formats around it differ on purpose
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"AA0CA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,uBAAuB,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,6BAA6B,CAAC;AAwB9E;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAA+B;IACzD,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,GAAG,CAAC;IAClC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACpC,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -1,9 +1,49 @@
|
|
|
1
|
-
import { ValidationReport } from './types.js';
|
|
1
|
+
import { ValidationReport, ValidationIssue, RebuiltSpecFailure } from './types.js';
|
|
2
|
+
import { type DeltaOperation } from './sister-changes.js';
|
|
3
|
+
/**
|
|
4
|
+
* The edit that writes `suggestedHeader` into the delta. A RENAMED names its
|
|
5
|
+
* requirement on the `- FROM:` bullet, and "change the header" sent the
|
|
6
|
+
* author to a `### Requirement:` line the entry does not have.
|
|
7
|
+
*/
|
|
8
|
+
export declare function headerCorrectionClause(operation: DeltaOperation, suggestedHeader: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* What a refusal's `suggestedHeader` corrects, for apply's fix: which line to
|
|
11
|
+
* edit (a RENAMED's is the FROM line) and whose spelling the name is. Kept
|
|
12
|
+
* off `ValidationIssue`: validate's message already says both, and apply's
|
|
13
|
+
* fix is the only text written from them.
|
|
14
|
+
*/
|
|
15
|
+
export interface HeaderCorrection {
|
|
16
|
+
operation: DeltaOperation;
|
|
17
|
+
/** A sister change's ADDED, which no archive order settles, or the main spec's own name. */
|
|
18
|
+
source: 'sister-change' | 'main-spec';
|
|
19
|
+
}
|
|
20
|
+
/** One delta archive's merge would refuse, as `findArchiveRefusals` reports it. */
|
|
21
|
+
export interface ArchiveRefusal {
|
|
22
|
+
issue: ValidationIssue;
|
|
23
|
+
/** Set exactly when `issue.suggestedHeader` is. */
|
|
24
|
+
headerCorrection?: HeaderCorrection;
|
|
25
|
+
}
|
|
2
26
|
export declare class Validator {
|
|
3
27
|
private strictMode;
|
|
4
28
|
constructor(strictMode?: boolean);
|
|
5
29
|
validateSpec(filePath: string): Promise<ValidationReport>;
|
|
6
30
|
validateSpecContent(specName: string, content: string): Promise<ValidationReport>;
|
|
31
|
+
/**
|
|
32
|
+
* Whether archive would refuse to write `rebuilt`, and whose fault that is.
|
|
33
|
+
* Null when it would write it.
|
|
34
|
+
*
|
|
35
|
+
* The verdict counts ERRORs only, whatever this validator's strictness:
|
|
36
|
+
* archive judges the rebuilt spec non-strictly, and this answers for archive.
|
|
37
|
+
*
|
|
38
|
+
* A main spec that already fails is the cause, even if the delta adds its own
|
|
39
|
+
* faults: those the change's validation reports anyway, while the main
|
|
40
|
+
* spec's are reported nowhere a change author looks — and its issue paths,
|
|
41
|
+
* unlike the rebuilt spec's, are the ones `tospec validate <spec>` prints.
|
|
42
|
+
*/
|
|
43
|
+
diagnoseRebuiltSpec(specName: string, rebuilt: string, original: {
|
|
44
|
+
path: string;
|
|
45
|
+
content: string;
|
|
46
|
+
} | undefined): Promise<RebuiltSpecFailure | null>;
|
|
7
47
|
/**
|
|
8
48
|
* Validate delta-formatted spec files under a change directory: at least one
|
|
9
49
|
* delta overall, ADDED/MODIFIED requirements carrying SHALL/MUST and a
|
|
@@ -17,9 +57,22 @@ export declare class Validator {
|
|
|
17
57
|
* `archivePreflight` is opt-out because the dry run rebuilds every updated
|
|
18
58
|
* spec. For `tospec validate` that is the point; for `archive` it is
|
|
19
59
|
* duplication, since the real merge follows moments later.
|
|
60
|
+
*
|
|
61
|
+
* `requireDelta: false` is for a change whose schema demands no delta. Every
|
|
62
|
+
* other check still runs, because archive merges each
|
|
63
|
+
* specs/<capability>/spec.md whatever the schema says. Dropped: the "at least
|
|
64
|
+
* one delta" ERROR, and the specs/spec.md ERROR for a file with no delta
|
|
65
|
+
* header, which such a schema leaves as notes, as at the rest of the root.
|
|
66
|
+
*
|
|
67
|
+
* `sisterChanges: false` drops the check against other in-progress changes'
|
|
68
|
+
* deltas. Archive turns it off: it runs its own check, in the direction the
|
|
69
|
+
* archive decides (this change goes first), where this one would advise
|
|
70
|
+
* archiving the other change first.
|
|
20
71
|
*/
|
|
21
72
|
validateChangeDeltaSpecs(changeDir: string, mainSpecsDir?: string, options?: {
|
|
22
73
|
archivePreflight?: boolean;
|
|
74
|
+
requireDelta?: boolean;
|
|
75
|
+
sisterChanges?: boolean;
|
|
23
76
|
}): Promise<ValidationReport>;
|
|
24
77
|
private convertZodErrors;
|
|
25
78
|
private applySpecRules;
|
|
@@ -36,6 +89,7 @@ export declare class Validator {
|
|
|
36
89
|
/**
|
|
37
90
|
* The main spec's two Purpose rules, applied to a delta that is about to
|
|
38
91
|
* create that main spec. Placeholder before brevity, as on the spec path.
|
|
92
|
+
* For an existing capability, only that a delta Purpose will be dropped.
|
|
39
93
|
*
|
|
40
94
|
* `mainSpecsDir` absent means new capabilities cannot be told from existing
|
|
41
95
|
* ones, so nothing is reported.
|
|
@@ -65,9 +119,32 @@ export declare class Validator {
|
|
|
65
119
|
* is missing is also what a change modifying a sibling's unarchived
|
|
66
120
|
* requirement looks like. INFO was wrong because `--strict` ignores it, so the
|
|
67
121
|
* pre-archive gate waved through changes archive would refuse.
|
|
68
|
-
* tospec/decisions/20260916_154000-archive-dry-run-findings-are-warnings.md
|
|
69
122
|
*/
|
|
70
123
|
private findArchiveBlockers;
|
|
124
|
+
/**
|
|
125
|
+
* The header mismatches behind a missing-target refusal: the one requirement
|
|
126
|
+
* a MODIFIED or RENAMED names, or, for a capability with no main spec, every
|
|
127
|
+
* rewrite in the delta, since the merge stops before reading any of them.
|
|
128
|
+
* Through `findHeaderMismatches`, so the refusal and the sister scan's
|
|
129
|
+
* ERROR cannot disagree about which headers mismatch.
|
|
130
|
+
*/
|
|
131
|
+
private findDeltaHeaderMismatches;
|
|
132
|
+
/**
|
|
133
|
+
* What archive's merge would refuse in this change, from the dry run above,
|
|
134
|
+
* for `instructions apply` to report before the work starts: it said ready
|
|
135
|
+
* for a delta validate already called unarchivable, so the agent learned it
|
|
136
|
+
* after implementing. One dry run serves both, so they cannot disagree.
|
|
137
|
+
*
|
|
138
|
+
* Without the structural pass's paths: apply reports none of its findings,
|
|
139
|
+
* so a delta fault that resurfaces in the rebuilt spec is the only word apply
|
|
140
|
+
* gets of it. Suppressed there as already reported, apply would say nothing.
|
|
141
|
+
*/
|
|
142
|
+
findArchiveRefusals(changeDir: string, mainSpecsDir: string): Promise<ArchiveRefusal[]>;
|
|
143
|
+
/**
|
|
144
|
+
* The rebuilt spec's validation failures, as archive would meet them. WARNING
|
|
145
|
+
* for the reason `findArchiveBlockers` gives.
|
|
146
|
+
*/
|
|
147
|
+
private findRebuiltSpecBlockers;
|
|
71
148
|
/**
|
|
72
149
|
* Scenario-quality rules for a delta requirement block: only `#### Scenario:`
|
|
73
150
|
* headers count, each scenario body must contain uppercase WHEN and THEN, and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/validator.ts"],"names":[],"mappings":"AAMA,OAAO,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/validator.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,gBAAgB,EAChB,eAAe,EAEf,kBAAkB,EAInB,MAAM,YAAY,CAAC;AA2CpB,OAAO,EAML,KAAK,cAAc,EAIpB,MAAM,qBAAqB,CAAC;AA6J7B;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAGjG;AA8DD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,cAAc,CAAC;IAC1B,4FAA4F;IAC5F,MAAM,EAAE,eAAe,GAAG,WAAW,CAAC;CACvC;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,eAAe,CAAC;IACvB,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAeD,qBAAa,SAAS;IACpB,OAAO,CAAC,UAAU,CAAU;IAE5B,YAAY,UAAU,GAAE,OAAe,EAEtC;IAEK,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAc9D;IAEK,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiBtF;IAED;;;;;;;;;;;OAWG;IACG,mBAAmB,CACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GACtD,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAUpC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,wBAAwB,CAC5B,SAAS,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,GAAE;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAO,GAC5F,OAAO,CAAC,gBAAgB,CAAC,CA0iB3B;IAED,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,cAAc;IA+EtB,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,sBAAsB;IAQ9B;;;;;OAKG;IACH,OAAO,CAAC,8BAA8B;IAuBtC;;;;;;;OAOG;YACW,yBAAyB;IAiEvC;;;;;;;;;;OAUG;YACW,4BAA4B;IA4J1C;;;;;;;;;;;;OAYG;YACW,mBAAmB;IAmIjC;;;;;;OAMG;YACW,yBAAyB;IAsBvC;;;;;;;;;OASG;IACG,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAK5F;IAED;;;OAGG;YACW,uBAAuB;IA4BrC;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAqE7B;;;;;OAKG;IACH,OAAO,CAAC,yBAAyB;CAalC"}
|