@theglitchking/hit-em-with-the-docs 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +5 -0
  4. package/commands/audit-facts.md +25 -0
  5. package/commands/cite.md +17 -0
  6. package/commands/extract-facts.md +66 -0
  7. package/commands/find-citers.md +16 -0
  8. package/commands/help.md +86 -0
  9. package/commands/migrate-incident.md +18 -0
  10. package/dist/action/core/audit/auditor.d.ts +14 -0
  11. package/dist/action/core/audit/auditor.d.ts.map +1 -1
  12. package/dist/action/core/domains/classifier.d.ts +15 -1
  13. package/dist/action/core/domains/classifier.d.ts.map +1 -1
  14. package/dist/action/core/knowledge-base/audit.d.ts +67 -0
  15. package/dist/action/core/knowledge-base/audit.d.ts.map +1 -0
  16. package/dist/action/core/knowledge-base/cite.d.ts +41 -0
  17. package/dist/action/core/knowledge-base/cite.d.ts.map +1 -0
  18. package/dist/action/core/knowledge-base/citers.d.ts +119 -0
  19. package/dist/action/core/knowledge-base/citers.d.ts.map +1 -0
  20. package/dist/action/core/knowledge-base/extract.d.ts +61 -0
  21. package/dist/action/core/knowledge-base/extract.d.ts.map +1 -0
  22. package/dist/action/core/knowledge-base/migrate.d.ts +33 -0
  23. package/dist/action/core/knowledge-base/migrate.d.ts.map +1 -0
  24. package/dist/action/core/maintain/orchestrator.d.ts.map +1 -1
  25. package/dist/action/core/metadata/errors.d.ts +62 -0
  26. package/dist/action/core/metadata/errors.d.ts.map +1 -0
  27. package/dist/action/core/metadata/schema.d.ts +810 -49
  28. package/dist/action/core/metadata/schema.d.ts.map +1 -1
  29. package/dist/action/generators/facts-index.d.ts +27 -0
  30. package/dist/action/generators/facts-index.d.ts.map +1 -0
  31. package/dist/action/generators/incidents-index.d.ts +21 -0
  32. package/dist/action/generators/incidents-index.d.ts.map +1 -0
  33. package/dist/action/generators/symptoms-index.d.ts +20 -0
  34. package/dist/action/generators/symptoms-index.d.ts.map +1 -0
  35. package/dist/action/generators/table.d.ts +8 -0
  36. package/dist/action/generators/table.d.ts.map +1 -0
  37. package/dist/action/index.d.ts +7 -0
  38. package/dist/action/index.d.ts.map +1 -1
  39. package/dist/action/index.js +4 -4
  40. package/dist/action/utils/config.d.ts +99 -0
  41. package/dist/action/utils/config.d.ts.map +1 -0
  42. package/dist/cli/index.js +248 -1
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/audit/auditor.d.ts +14 -0
  45. package/dist/core/audit/auditor.d.ts.map +1 -1
  46. package/dist/core/audit/auditor.js +19 -3
  47. package/dist/core/audit/auditor.js.map +1 -1
  48. package/dist/core/domains/classifier.d.ts +15 -1
  49. package/dist/core/domains/classifier.d.ts.map +1 -1
  50. package/dist/core/domains/classifier.js +100 -1
  51. package/dist/core/domains/classifier.js.map +1 -1
  52. package/dist/core/knowledge-base/audit.d.ts +67 -0
  53. package/dist/core/knowledge-base/audit.d.ts.map +1 -0
  54. package/dist/core/knowledge-base/audit.js +158 -0
  55. package/dist/core/knowledge-base/audit.js.map +1 -0
  56. package/dist/core/knowledge-base/cite.d.ts +41 -0
  57. package/dist/core/knowledge-base/cite.d.ts.map +1 -0
  58. package/dist/core/knowledge-base/cite.js +82 -0
  59. package/dist/core/knowledge-base/cite.js.map +1 -0
  60. package/dist/core/knowledge-base/citers.d.ts +119 -0
  61. package/dist/core/knowledge-base/citers.d.ts.map +1 -0
  62. package/dist/core/knowledge-base/citers.js +249 -0
  63. package/dist/core/knowledge-base/citers.js.map +1 -0
  64. package/dist/core/knowledge-base/extract.d.ts +61 -0
  65. package/dist/core/knowledge-base/extract.d.ts.map +1 -0
  66. package/dist/core/knowledge-base/extract.js +132 -0
  67. package/dist/core/knowledge-base/extract.js.map +1 -0
  68. package/dist/core/knowledge-base/migrate.d.ts +33 -0
  69. package/dist/core/knowledge-base/migrate.d.ts.map +1 -0
  70. package/dist/core/knowledge-base/migrate.js +112 -0
  71. package/dist/core/knowledge-base/migrate.js.map +1 -0
  72. package/dist/core/maintain/orchestrator.d.ts.map +1 -1
  73. package/dist/core/maintain/orchestrator.js +48 -1
  74. package/dist/core/maintain/orchestrator.js.map +1 -1
  75. package/dist/core/metadata/errors.d.ts +62 -0
  76. package/dist/core/metadata/errors.d.ts.map +1 -0
  77. package/dist/core/metadata/errors.js +95 -0
  78. package/dist/core/metadata/errors.js.map +1 -0
  79. package/dist/core/metadata/schema.d.ts +806 -45
  80. package/dist/core/metadata/schema.d.ts.map +1 -1
  81. package/dist/core/metadata/schema.js +472 -42
  82. package/dist/core/metadata/schema.js.map +1 -1
  83. package/dist/generators/facts-index.d.ts +27 -0
  84. package/dist/generators/facts-index.d.ts.map +1 -0
  85. package/dist/generators/facts-index.js +111 -0
  86. package/dist/generators/facts-index.js.map +1 -0
  87. package/dist/generators/incidents-index.d.ts +21 -0
  88. package/dist/generators/incidents-index.d.ts.map +1 -0
  89. package/dist/generators/incidents-index.js +79 -0
  90. package/dist/generators/incidents-index.js.map +1 -0
  91. package/dist/generators/symptoms-index.d.ts +20 -0
  92. package/dist/generators/symptoms-index.d.ts.map +1 -0
  93. package/dist/generators/symptoms-index.js +123 -0
  94. package/dist/generators/symptoms-index.js.map +1 -0
  95. package/dist/generators/table.d.ts +8 -0
  96. package/dist/generators/table.d.ts.map +1 -0
  97. package/dist/generators/table.js +23 -0
  98. package/dist/generators/table.js.map +1 -0
  99. package/dist/index.d.ts +7 -0
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +8 -0
  102. package/dist/index.js.map +1 -1
  103. package/dist/utils/config.d.ts +99 -0
  104. package/dist/utils/config.d.ts.map +1 -0
  105. package/dist/utils/config.js +95 -0
  106. package/dist/utils/config.js.map +1 -0
  107. package/package.json +1 -1
  108. package/templates/knowledge-base/README.md +31 -0
  109. package/templates/knowledge-base/fact.template.md +44 -0
  110. package/templates/knowledge-base/incident-facts.template.md +31 -0
  111. package/templates/knowledge-base/incident-narrative.template.md +50 -0
  112. package/templates/knowledge-base/playbook-symptoms.template.md +74 -0
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/core/metadata/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqDxB;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKE,CAAC;AAE9B,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA2B,CAAC;AAC9D,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE5E;;GAEG;AACH,eAAO,MAAM,eAAe,4EAOlB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,OAAO,CAAC,gBAAgB,CAI9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,+EAKxB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,mBAAmB,mTA4BtB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG;IAC/C,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAYA;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG;IACtD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,uBAAuB,CAAC;CAChC,CAYA;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,EAAE,CAIV;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAY5C;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,GACnB,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,gBAAgB,GAAG,MAAM,CAiB3E"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/core/metadata/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAEL,uBAAuB,EACvB,sBAAsB,EACtB,KAAK,IAAI,EACV,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAiC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAmC9E,QAAA,MAAM,iBAAiB,wDAAyD,CAAC;AAwDjF,QAAA,MAAM,sBAAsB,kDAAmD,CAAC;AAChF,QAAA,MAAM,wBAAwB,gDAAiD,CAAC;AAChF,QAAA,MAAM,0BAA0B,qDAAsD,CAAC;AAEvF,QAAA,MAAM,aAAa,QAA+B,CAAC;AACnD,QAAA,MAAM,cAAc,QAAiD,CAAC;AAyItE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCvB,CAAC;AAEL,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA8B,CAAC;AACjE,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE5E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,4EAOlB,CAAC;AAEX;;;;;GAKG;AACH,QAAA,MAAM,wBAAwB,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC,CAUtE,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,OAAO,CAAC,gBAAgB,CAI9D,CAAC;AAEF,eAAO,MAAM,qBAAqB,+EAKxB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mTA4BtB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG;IAC/C,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAYA;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAwBD,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,iBAAiB,EAAE,CAyLrB;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG;IACtD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,uBAAuB,CAAC;CAChC,CAYA;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,EAAE,CAwBV;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAU5C;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAMR;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,GACnB,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,gBAAgB,GAAG,MAAM,CAiB3E;AAGD,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,aAAa,EACb,cAAc,EACd,wBAAwB,GACzB,CAAC"}
@@ -1,10 +1,29 @@
1
1
  import { z } from 'zod';
2
- import { TIERS } from '../domains/classifier.js';
2
+ import { TIERS, LIFECYCLE_TRACKED_TIERS, isLifecycleTrackedTier, } from '../domains/classifier.js';
3
+ import { KB_ERROR_CODES, formatKbError } from './errors.js';
3
4
  /**
4
- * 22-field metadata schema for documentation files.
5
- * This schema validates the YAML frontmatter of documentation files.
5
+ * Metadata schema for documentation files (the YAML frontmatter contract).
6
+ *
7
+ * Universal fields: 22 across six groups (core / status / discovery / ownership
8
+ * / implementation / auto). Plus tier-specific extensions for the lifecycle-
9
+ * tracked tiers introduced in 2.2.0 (plan) and 2.3.0 (fact, incident-narrative,
10
+ * incident-facts), and an optional `symptoms:` field for playbook documents.
11
+ *
12
+ * Conditional rules (enforced by .refine() on the merged MetadataSchema):
13
+ *
14
+ * 1. `version` is optional in the base schema; required for tiers that
15
+ * AREN'T in LIFECYCLE_TRACKED_TIERS. Lifecycle-tracked tiers (plan, fact,
16
+ * incident-narrative, incident-facts) use status/last_verified, not semver.
17
+ * 2. `status` accepts any string in the base schema; conditionally restricted
18
+ * to the doc-tier enum (draft|active|deprecated|archived) for tiers that
19
+ * aren't lifecycle-tracked. Each lifecycle-tracked tier uses its own
20
+ * status enum.
21
+ * 3. Each lifecycle-tracked tier has its own required-fields contract
22
+ * (e.g. fact requires id+confidence+last_verified+provenance).
23
+ * 4. The optional `symptoms:` field, when present on any tier, must be an
24
+ * array of valid symptom entries.
6
25
  */
7
- // Core Identity Fields (Required)
26
+ // ----- Core schema groups (existing 22-field surface) -----
8
27
  const coreIdentitySchema = z.object({
9
28
  title: z.string().min(1, 'Title is required'),
10
29
  tier: z.enum(TIERS),
@@ -12,52 +31,230 @@ const coreIdentitySchema = z.object({
12
31
  audience: z.array(z.enum(['all', 'developers', 'devops', 'admin'])).default(['all']),
13
32
  tags: z.array(z.string()).default([]),
14
33
  });
15
- // Status & Lifecycle Fields (Required)
34
+ const DOC_STATUS_VALUES = ['draft', 'active', 'deprecated', 'archived'];
35
+ /**
36
+ * Date-shaped value preprocessor: js-yaml auto-converts unquoted `2026-05-14`
37
+ * style fields into Date objects. Real users writing markdown rarely quote
38
+ * their dates, so we accept both `Date` and string-formatted YYYY-MM-DD,
39
+ * normalizing to the latter. Added in 2.3.0 alongside the knowledge-base
40
+ * primitives; applied universally so existing tiers (with unquoted
41
+ * `last_updated:` fields) also benefit.
42
+ */
43
+ const dateStringSchema = z.preprocess((v) => {
44
+ if (v instanceof Date && !Number.isNaN(v.getTime())) {
45
+ return v.toISOString().slice(0, 10);
46
+ }
47
+ return v;
48
+ }, z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be YYYY-MM-DD format'));
16
49
  const statusSchema = z.object({
17
- status: z.enum(['draft', 'active', 'deprecated', 'archived']),
18
- last_updated: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be YYYY-MM-DD format'),
19
- version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Must be semver format (X.Y.Z)'),
50
+ status: z.string().min(1, 'status is required'),
51
+ last_updated: dateStringSchema,
52
+ version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Must be semver format (X.Y.Z)').optional(),
20
53
  });
21
- // Discovery & Navigation Fields (Optional)
22
54
  const discoverySchema = z.object({
23
55
  purpose: z.string().optional(),
24
56
  related_docs: z.array(z.string()).optional(),
25
57
  load_priority: z.number().min(1).max(10).optional(),
26
58
  });
27
- // Ownership & Maintenance Fields (Optional)
28
59
  const ownershipSchema = z.object({
29
60
  author: z.string().optional(),
30
61
  maintainer: z.string().optional(),
31
62
  review_frequency: z.enum(['weekly', 'monthly', 'quarterly', 'annually']).optional(),
32
63
  });
33
- // Implementation Fields (Optional)
34
64
  const implementationSchema = z.object({
35
65
  implementation_status: z.enum(['planned', 'in_progress', 'complete', 'deprecated']).optional(),
36
66
  tested: z.boolean().optional(),
37
67
  production_ready: z.boolean().optional(),
38
68
  });
39
- // Auto-generated Fields
40
69
  const autoGeneratedSchema = z.object({
41
70
  estimated_read_time: z.string().optional(),
42
71
  word_count: z.number().optional(),
43
- last_validated: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
72
+ last_validated: dateStringSchema.optional(),
44
73
  backlinks: z.array(z.string()).optional(),
45
74
  });
46
- /**
47
- * Complete 22-field metadata schema
48
- */
49
- export const MetadataSchema = coreIdentitySchema
75
+ // ----- Knowledge-base extension fields (2.3.0) -----
76
+ //
77
+ // Every field below is optional at the type level — the conditional
78
+ // .refine() rules on the merged MetadataSchema enforce per-tier requirements.
79
+ const FACT_CONFIDENCE_VALUES = ['high', 'medium', 'low', 'hypothesis'];
80
+ const INCIDENT_SEVERITY_VALUES = ['low', 'medium', 'high', 'critical'];
81
+ const INCIDENT_RESOLUTION_VALUES = ['resolved', 'partial', 'open', 'planned'];
82
+ const KEBAB_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
83
+ const INCIDENT_ID_RE = /^\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*$/;
84
+ const knowledgeBaseSchema = z.object({
85
+ // Fact fields
86
+ id: z.string().min(1).optional(),
87
+ confidence: z.string().optional(),
88
+ last_verified: dateStringSchema.optional(),
89
+ verify_command: z.string().optional(),
90
+ provenance: z.array(z.string()).optional(),
91
+ sources: z.array(z.string()).optional(),
92
+ invalidated_by: z.array(z.string()).optional(),
93
+ working_dir: z.string().optional(),
94
+ // Incident-narrative fields
95
+ date: dateStringSchema.optional(),
96
+ severity: z.string().optional(),
97
+ resolution_status: z.string().optional(),
98
+ components: z.array(z.string()).optional(),
99
+ // Incident-facts fields
100
+ incident_id: z.string().optional(),
101
+ produced: z.array(z.string()).optional(),
102
+ strengthened: z.array(z.string()).optional(),
103
+ weakened: z.array(z.string()).optional(),
104
+ // Playbook symptoms (any tier may carry this)
105
+ symptoms: z
106
+ .array(z.object({
107
+ alert_name: z.string().optional(),
108
+ user_phrase: z.union([z.string(), z.array(z.string())]).optional(),
109
+ error_pattern: z.string().optional(),
110
+ severity: z.enum(INCIDENT_SEVERITY_VALUES).optional(),
111
+ target: z.string().min(1).optional(),
112
+ cites: z.array(z.string()).optional(),
113
+ }))
114
+ .optional(),
115
+ });
116
+ // ----- Merged base (no refinements yet) -----
117
+ const _baseMergedSchema = coreIdentitySchema
50
118
  .merge(statusSchema)
51
119
  .merge(discoverySchema)
52
120
  .merge(ownershipSchema)
53
121
  .merge(implementationSchema)
54
- .merge(autoGeneratedSchema);
122
+ .merge(autoGeneratedSchema)
123
+ .merge(knowledgeBaseSchema);
124
+ // ----- Conditional refinements -----
55
125
  /**
56
- * Partial schema for documents that may have incomplete metadata
126
+ * `version` is required for any tier NOT in LIFECYCLE_TRACKED_TIERS.
57
127
  */
58
- export const PartialMetadataSchema = MetadataSchema.partial();
128
+ const refineVersionRequired = (data) => isLifecycleTrackedTier(data.tier) || data.version !== undefined;
59
129
  /**
60
- * Required fields that must always be present
130
+ * `status` must be one of DOC_STATUS_VALUES for non-lifecycle-tracked tiers.
131
+ * Lifecycle-tracked tiers use their own status enums (free-form at this layer).
132
+ */
133
+ const refineStatusEnum = (data) => isLifecycleTrackedTier(data.tier) ||
134
+ DOC_STATUS_VALUES.includes(data.status);
135
+ /**
136
+ * Fact-tier required fields: id, confidence (enum), last_verified, provenance (≥1).
137
+ */
138
+ const refineFactRequirements = (data) => {
139
+ if (data.tier !== 'fact')
140
+ return true;
141
+ const checks = [
142
+ typeof data.id === 'string' && data.id.length > 0,
143
+ typeof data.confidence === 'string' &&
144
+ FACT_CONFIDENCE_VALUES.includes(data.confidence),
145
+ typeof data.last_verified === 'string',
146
+ Array.isArray(data.provenance) && data.provenance.length > 0,
147
+ ];
148
+ return checks.every(Boolean);
149
+ };
150
+ /**
151
+ * Incident-narrative required fields: id, date, severity (enum),
152
+ * resolution_status (enum), components (array).
153
+ */
154
+ const refineIncidentNarrativeRequirements = (data) => {
155
+ if (data.tier !== 'incident-narrative')
156
+ return true;
157
+ const checks = [
158
+ typeof data.id === 'string' && data.id.length > 0,
159
+ typeof data.date === 'string',
160
+ typeof data.severity === 'string' &&
161
+ INCIDENT_SEVERITY_VALUES.includes(data.severity),
162
+ typeof data.resolution_status === 'string' &&
163
+ INCIDENT_RESOLUTION_VALUES.includes(data.resolution_status),
164
+ Array.isArray(data.components),
165
+ ];
166
+ return checks.every(Boolean);
167
+ };
168
+ /**
169
+ * Incident-facts required fields: incident_id, produced (array, may be empty).
170
+ */
171
+ const refineIncidentFactsRequirements = (data) => {
172
+ if (data.tier !== 'incident-facts')
173
+ return true;
174
+ const checks = [
175
+ typeof data.incident_id === 'string' && data.incident_id.length > 0,
176
+ Array.isArray(data.produced),
177
+ ];
178
+ return checks.every(Boolean);
179
+ };
180
+ /**
181
+ * Symptoms entries (any tier): each must have one of alert_name/user_phrase/
182
+ * error_pattern AND target AND non-empty cites array.
183
+ */
184
+ const refineSymptomsEntries = (data) => {
185
+ if (!Array.isArray(data.symptoms))
186
+ return true;
187
+ for (const s of data.symptoms) {
188
+ const hasKey = s.alert_name !== undefined ||
189
+ s.user_phrase !== undefined ||
190
+ s.error_pattern !== undefined;
191
+ if (!hasKey)
192
+ return false;
193
+ if (typeof s.target !== 'string' || s.target.length === 0)
194
+ return false;
195
+ if (!Array.isArray(s.cites) || s.cites.length === 0)
196
+ return false;
197
+ }
198
+ return true;
199
+ };
200
+ // ----- Exported schema -----
201
+ /**
202
+ * Complete metadata schema with all conditional refinements applied.
203
+ *
204
+ * Required fields by tier:
205
+ * - Non-lifecycle-tracked (guide/standard/example/reference/admin):
206
+ * title, tier, domains, status (enum), last_updated, version
207
+ * - plan: title, tier, domains, status, last_updated (no version)
208
+ * - fact: title, tier, domains, status, last_updated, id, confidence,
209
+ * last_verified, provenance (no version)
210
+ * - incident-narrative: title, tier, domains, status, last_updated,
211
+ * id, date, severity, resolution_status, components (no version)
212
+ * - incident-facts: title, tier, domains, status, last_updated,
213
+ * incident_id, produced (no version)
214
+ */
215
+ export const MetadataSchema = _baseMergedSchema
216
+ .refine(refineVersionRequired, {
217
+ message: 'version is required unless tier is plan, fact, incident-narrative, or incident-facts',
218
+ path: ['version'],
219
+ })
220
+ .refine(refineStatusEnum, {
221
+ message: `status must be one of ${DOC_STATUS_VALUES.join('|')} for non-lifecycle-tracked tiers`,
222
+ path: ['status'],
223
+ })
224
+ .refine(refineFactRequirements, {
225
+ message: formatKbError(KB_ERROR_CODES.FACT_MISSING_ID, 'fact tier requires id (kebab-case), confidence (high|medium|low|hypothesis), last_verified (YYYY-MM-DD), and provenance (≥1 entry)'),
226
+ path: ['fact'],
227
+ })
228
+ .refine(refineIncidentNarrativeRequirements, {
229
+ message: formatKbError(KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_DATE, 'incident-narrative tier requires id, date (YYYY-MM-DD), severity (low|medium|high|critical), resolution_status (resolved|partial|open|planned), and components (array)'),
230
+ path: ['incident-narrative'],
231
+ })
232
+ .refine(refineIncidentFactsRequirements, {
233
+ message: formatKbError(KB_ERROR_CODES.INCIDENT_FACTS_MISSING_INCIDENT_ID, 'incident-facts tier requires incident_id and produced (array, may be empty)'),
234
+ path: ['incident-facts'],
235
+ })
236
+ .refine(refineSymptomsEntries, {
237
+ message: formatKbError(KB_ERROR_CODES.PLAYBOOK_SYMPTOM_MISSING_KEY, 'each symptoms[] entry requires one of alert_name|user_phrase|error_pattern AND a target AND a non-empty cites[] array'),
238
+ path: ['symptoms'],
239
+ });
240
+ /**
241
+ * Partial schema for documents that may have incomplete metadata.
242
+ * Built from the un-refined base so every field is optional regardless of
243
+ * tier — used by `hewtd fix` to identify incomplete docs.
244
+ */
245
+ export const PartialMetadataSchema = _baseMergedSchema.partial();
246
+ /**
247
+ * Required fields shared across all tiers.
248
+ *
249
+ * Note: `version` is conditionally required — it must be present for all
250
+ * tiers NOT in LIFECYCLE_TRACKED_TIERS. The list below preserves the
251
+ * historical 6-required-field contract for non-lifecycle docs; for lifecycle-
252
+ * tracked docs (plan/fact/incident-narrative/incident-facts), `version` is
253
+ * dropped and the per-tier extension fields are added by
254
+ * `getRequiredFieldsForTier()`.
255
+ *
256
+ * Prefer `getMissingRequiredFields(data)` for tier-aware checks rather than
257
+ * iterating REQUIRED_FIELDS directly.
61
258
  */
62
259
  export const REQUIRED_FIELDS = [
63
260
  'title',
@@ -68,16 +265,27 @@ export const REQUIRED_FIELDS = [
68
265
  'version',
69
266
  ];
70
267
  /**
71
- * Optional fields with their default values
268
+ * Tier-specific required-field extensions for lifecycle-tracked tiers.
269
+ *
270
+ * For each tier, these fields are required IN ADDITION to the universal
271
+ * five (title, tier, domains, status, last_updated). `version` is excluded.
72
272
  */
273
+ const TIER_REQUIRED_EXTENSIONS = {
274
+ fact: ['id', 'confidence', 'last_verified', 'provenance'],
275
+ 'incident-narrative': [
276
+ 'id',
277
+ 'date',
278
+ 'severity',
279
+ 'resolution_status',
280
+ 'components',
281
+ ],
282
+ 'incident-facts': ['incident_id', 'produced'],
283
+ };
73
284
  export const OPTIONAL_FIELDS_DEFAULTS = {
74
285
  audience: ['all'],
75
286
  tags: [],
76
287
  backlinks: [],
77
288
  };
78
- /**
79
- * Auto-generated fields that should not be manually edited
80
- */
81
289
  export const AUTO_GENERATED_FIELDS = [
82
290
  'estimated_read_time',
83
291
  'word_count',
@@ -85,7 +293,9 @@ export const AUTO_GENERATED_FIELDS = [
85
293
  'backlinks',
86
294
  ];
87
295
  /**
88
- * All 22 field names
296
+ * All 22 universal field names (unchanged from 2.2.0). The 2.3.0 knowledge-
297
+ * base extension fields (id, confidence, etc.) are intentionally NOT in this
298
+ * list — they apply per-tier and would skew completeness calc on non-KB docs.
89
299
  */
90
300
  export const ALL_METADATA_FIELDS = [
91
301
  // Core Identity (5)
@@ -116,9 +326,6 @@ export const ALL_METADATA_FIELDS = [
116
326
  'last_validated',
117
327
  'backlinks',
118
328
  ];
119
- /**
120
- * Validate metadata against the schema
121
- */
122
329
  export function validateMetadata(data) {
123
330
  const result = MetadataSchema.safeParse(data);
124
331
  if (result.success) {
@@ -128,8 +335,212 @@ export function validateMetadata(data) {
128
335
  return { valid: false, errors };
129
336
  }
130
337
  /**
131
- * Validate partial metadata (for documents being fixed)
338
+ * Run all KB-specific validation predicates against a partial metadata object
339
+ * and return a typed error list. This is the entrypoint the audit layer uses
340
+ * for the knowledge-base primitives (fact / incident-narrative / incident-facts
341
+ * / symptoms).
342
+ *
343
+ * Unlike `validateMetadata()`, this does NOT require the base schema to pass
344
+ * first — it can run on partial / in-progress docs and surface ONLY the KB
345
+ * coded errors, which lets the auditor combine KB checks with its existing
346
+ * missing-required-fields reporting without duplicate output.
132
347
  */
348
+ /**
349
+ * YAML dates (e.g. `date: 2026-05-14` unquoted) are auto-parsed by js-yaml
350
+ * into Date objects. We accept both `string` (YYYY-MM-DD) and `Date` as
351
+ * valid date-shaped values, so users don't have to quote dates manually.
352
+ */
353
+ function isDateLike(value) {
354
+ if (typeof value === 'string')
355
+ return /^\d{4}-\d{2}-\d{2}$/.test(value);
356
+ if (value instanceof Date && !Number.isNaN(value.getTime()))
357
+ return true;
358
+ return false;
359
+ }
360
+ export function validateKnowledgeBaseFields(data) {
361
+ const errors = [];
362
+ const tier = typeof data.tier === 'string' ? data.tier : undefined;
363
+ // ----- Fact tier -----
364
+ if (tier === 'fact') {
365
+ if (typeof data.id !== 'string' || data.id.length === 0) {
366
+ errors.push({
367
+ code: KB_ERROR_CODES.FACT_MISSING_ID,
368
+ message: 'fact tier requires an id (kebab-case slug)',
369
+ path: 'id',
370
+ });
371
+ }
372
+ else if (!KEBAB_SLUG_RE.test(data.id)) {
373
+ errors.push({
374
+ code: KB_ERROR_CODES.FACT_MISSING_ID,
375
+ message: `fact id must be kebab-case (got: ${data.id})`,
376
+ path: 'id',
377
+ });
378
+ }
379
+ if (typeof data.confidence !== 'string') {
380
+ errors.push({
381
+ code: KB_ERROR_CODES.FACT_INVALID_CONFIDENCE,
382
+ message: `fact tier requires confidence (one of ${FACT_CONFIDENCE_VALUES.join('|')})`,
383
+ path: 'confidence',
384
+ });
385
+ }
386
+ else if (!FACT_CONFIDENCE_VALUES.includes(data.confidence)) {
387
+ errors.push({
388
+ code: KB_ERROR_CODES.FACT_INVALID_CONFIDENCE,
389
+ message: `confidence must be one of ${FACT_CONFIDENCE_VALUES.join('|')} (got: ${data.confidence})`,
390
+ path: 'confidence',
391
+ });
392
+ }
393
+ if (data.last_verified === undefined || data.last_verified === null) {
394
+ errors.push({
395
+ code: KB_ERROR_CODES.FACT_MISSING_LAST_VERIFIED,
396
+ message: 'fact tier requires last_verified (YYYY-MM-DD)',
397
+ path: 'last_verified',
398
+ });
399
+ }
400
+ else if (!isDateLike(data.last_verified)) {
401
+ errors.push({
402
+ code: KB_ERROR_CODES.FACT_MISSING_LAST_VERIFIED,
403
+ message: `last_verified must be YYYY-MM-DD (got: ${String(data.last_verified)})`,
404
+ path: 'last_verified',
405
+ });
406
+ }
407
+ if (!Array.isArray(data.provenance) || data.provenance.length === 0) {
408
+ errors.push({
409
+ code: KB_ERROR_CODES.FACT_MISSING_PROVENANCE,
410
+ message: 'fact tier requires at least one provenance entry',
411
+ path: 'provenance',
412
+ });
413
+ }
414
+ // Warning: verify_command with TRULY multi-line content (multiple commands)
415
+ // but missing shebang. YAML's `|` block scalar leaves a trailing newline
416
+ // on single-line commands, so we trim first to avoid false positives.
417
+ if (typeof data.verify_command === 'string') {
418
+ const trimmed = data.verify_command.trim();
419
+ if (trimmed.includes('\n') && !trimmed.startsWith('#!')) {
420
+ errors.push({
421
+ code: KB_ERROR_CODES.FACT_VERIFY_COMMAND_MULTILINE_SHEBANG,
422
+ message: 'multi-line verify_command without a shebang line — fine, but consider adding `#!/usr/bin/env bash` for clarity',
423
+ path: 'verify_command',
424
+ });
425
+ }
426
+ }
427
+ }
428
+ // ----- Incident-narrative tier -----
429
+ if (tier === 'incident-narrative') {
430
+ if (typeof data.id !== 'string' || data.id.length === 0) {
431
+ errors.push({
432
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_DATE,
433
+ message: 'incident-narrative tier requires an id (YYYY-MM-DD-slug)',
434
+ path: 'id',
435
+ });
436
+ }
437
+ else if (!INCIDENT_ID_RE.test(data.id)) {
438
+ errors.push({
439
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_DATE,
440
+ message: `incident id must match YYYY-MM-DD-<slug> (got: ${data.id})`,
441
+ path: 'id',
442
+ });
443
+ }
444
+ if (data.date === undefined || data.date === null) {
445
+ errors.push({
446
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_DATE,
447
+ message: 'incident-narrative tier requires date (YYYY-MM-DD)',
448
+ path: 'date',
449
+ });
450
+ }
451
+ else if (!isDateLike(data.date)) {
452
+ errors.push({
453
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_DATE,
454
+ message: `date must be YYYY-MM-DD (got: ${String(data.date)})`,
455
+ path: 'date',
456
+ });
457
+ }
458
+ if (typeof data.severity !== 'string') {
459
+ errors.push({
460
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_SEVERITY,
461
+ message: `incident-narrative tier requires severity (${INCIDENT_SEVERITY_VALUES.join('|')})`,
462
+ path: 'severity',
463
+ });
464
+ }
465
+ else if (!INCIDENT_SEVERITY_VALUES.includes(data.severity)) {
466
+ errors.push({
467
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_INVALID_SEVERITY,
468
+ message: `severity must be one of ${INCIDENT_SEVERITY_VALUES.join('|')} (got: ${data.severity})`,
469
+ path: 'severity',
470
+ });
471
+ }
472
+ if (typeof data.resolution_status !== 'string') {
473
+ errors.push({
474
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_RESOLUTION_STATUS,
475
+ message: `incident-narrative tier requires resolution_status (${INCIDENT_RESOLUTION_VALUES.join('|')})`,
476
+ path: 'resolution_status',
477
+ });
478
+ }
479
+ else if (!INCIDENT_RESOLUTION_VALUES.includes(data.resolution_status)) {
480
+ errors.push({
481
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_INVALID_RESOLUTION_STATUS,
482
+ message: `resolution_status must be one of ${INCIDENT_RESOLUTION_VALUES.join('|')} (got: ${data.resolution_status})`,
483
+ path: 'resolution_status',
484
+ });
485
+ }
486
+ if (!Array.isArray(data.components) || data.components.length === 0) {
487
+ errors.push({
488
+ code: KB_ERROR_CODES.INCIDENT_NARRATIVE_MISSING_COMPONENTS,
489
+ message: 'incident-narrative tier requires at least one component',
490
+ path: 'components',
491
+ });
492
+ }
493
+ }
494
+ // ----- Incident-facts tier -----
495
+ if (tier === 'incident-facts') {
496
+ if (typeof data.incident_id !== 'string' || data.incident_id.length === 0) {
497
+ errors.push({
498
+ code: KB_ERROR_CODES.INCIDENT_FACTS_MISSING_INCIDENT_ID,
499
+ message: 'incident-facts tier requires incident_id',
500
+ path: 'incident_id',
501
+ });
502
+ }
503
+ if (!Array.isArray(data.produced)) {
504
+ errors.push({
505
+ code: KB_ERROR_CODES.INCIDENT_FACTS_MISSING_PRODUCED,
506
+ message: 'incident-facts tier requires produced (array, may be empty)',
507
+ path: 'produced',
508
+ });
509
+ }
510
+ }
511
+ // ----- Symptoms entries (any tier) -----
512
+ if (Array.isArray(data.symptoms)) {
513
+ for (let i = 0; i < data.symptoms.length; i++) {
514
+ const s = data.symptoms[i];
515
+ const hasKey = typeof s.alert_name === 'string' ||
516
+ typeof s.user_phrase === 'string' ||
517
+ Array.isArray(s.user_phrase) ||
518
+ typeof s.error_pattern === 'string';
519
+ if (!hasKey) {
520
+ errors.push({
521
+ code: KB_ERROR_CODES.PLAYBOOK_SYMPTOM_MISSING_KEY,
522
+ message: `symptoms[${i}] must have one of alert_name, user_phrase, or error_pattern`,
523
+ path: `symptoms[${i}]`,
524
+ });
525
+ }
526
+ if (typeof s.target !== 'string' || s.target.length === 0) {
527
+ errors.push({
528
+ code: KB_ERROR_CODES.PLAYBOOK_SYMPTOM_MISSING_TARGET,
529
+ message: `symptoms[${i}] requires a target (anchor or path)`,
530
+ path: `symptoms[${i}].target`,
531
+ });
532
+ }
533
+ if (!Array.isArray(s.cites) || s.cites.length === 0) {
534
+ errors.push({
535
+ code: KB_ERROR_CODES.PLAYBOOK_SYMPTOM_MISSING_CITES,
536
+ message: `symptoms[${i}] requires at least one cite (fact id)`,
537
+ path: `symptoms[${i}].cites`,
538
+ });
539
+ }
540
+ }
541
+ }
542
+ return errors;
543
+ }
133
544
  export function validatePartialMetadata(data) {
134
545
  const result = PartialMetadataSchema.safeParse(data);
135
546
  if (result.success) {
@@ -139,30 +550,47 @@ export function validatePartialMetadata(data) {
139
550
  return { valid: false, errors };
140
551
  }
141
552
  /**
142
- * Get missing required fields
553
+ * Get missing required fields, respecting both the version-exemption for
554
+ * lifecycle-tracked tiers AND the tier-specific extension requirements.
555
+ *
556
+ * - For non-lifecycle tiers: REQUIRED_FIELDS (all six, including version).
557
+ * - For lifecycle-tracked tiers: REQUIRED_FIELDS minus 'version', plus
558
+ * any tier-specific extensions from TIER_REQUIRED_EXTENSIONS.
143
559
  */
144
560
  export function getMissingRequiredFields(data) {
145
- return REQUIRED_FIELDS.filter((field) => !(field in data) || data[field] === undefined || data[field] === null);
561
+ const tier = typeof data.tier === 'string' ? data.tier : undefined;
562
+ const isLifecycle = isLifecycleTrackedTier(tier);
563
+ const extensions = tier
564
+ ? (TIER_REQUIRED_EXTENSIONS[tier] ?? [])
565
+ : [];
566
+ const baseRequired = REQUIRED_FIELDS.filter((field) => {
567
+ if (field === 'version' && isLifecycle)
568
+ return false;
569
+ return true;
570
+ });
571
+ const allRequired = [...baseRequired, ...extensions];
572
+ return allRequired.filter((field) => {
573
+ const value = data[field];
574
+ if (value === undefined || value === null)
575
+ return true;
576
+ if (Array.isArray(value) && value.length === 0) {
577
+ // provenance and components are required to be non-empty arrays;
578
+ // produced (incident-facts) is allowed to be empty.
579
+ return field === 'provenance' || field === 'components';
580
+ }
581
+ return false;
582
+ });
146
583
  }
147
- /**
148
- * Get all missing fields (both required and optional)
149
- */
150
584
  export function getMissingFields(data) {
151
- const required = REQUIRED_FIELDS.filter((field) => !(field in data) || data[field] === undefined);
585
+ const required = getMissingRequiredFields(data);
152
586
  const optional = ALL_METADATA_FIELDS.filter((field) => !REQUIRED_FIELDS.includes(field) &&
153
587
  !(field in data));
154
588
  return { required, optional };
155
589
  }
156
- /**
157
- * Calculate metadata completeness percentage
158
- */
159
590
  export function calculateMetadataCompleteness(data) {
160
591
  const presentFields = ALL_METADATA_FIELDS.filter((field) => field in data && data[field] !== undefined && data[field] !== null);
161
592
  return (presentFields.length / ALL_METADATA_FIELDS.length) * 100;
162
593
  }
163
- /**
164
- * Get field category
165
- */
166
594
  export function getFieldCategory(field) {
167
595
  if (['title', 'tier', 'domains', 'audience', 'tags'].includes(field)) {
168
596
  return 'core';
@@ -181,4 +609,6 @@ export function getFieldCategory(field) {
181
609
  }
182
610
  return 'auto';
183
611
  }
612
+ // Re-export tier-related symbols that consumers (and the auditor) need.
613
+ export { LIFECYCLE_TRACKED_TIERS, isLifecycleTrackedTier, DOC_STATUS_VALUES, FACT_CONFIDENCE_VALUES, INCIDENT_SEVERITY_VALUES, INCIDENT_RESOLUTION_VALUES, KEBAB_SLUG_RE, INCIDENT_ID_RE, TIER_REQUIRED_EXTENSIONS, };
184
614
  //# sourceMappingURL=schema.js.map