@contentrain/mcp 1.10.1 → 1.11.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 (59) hide show
  1. package/dist/{apply-manager-UXUTYFZn.mjs → apply-manager-tRqDkzBV.mjs} +6 -6
  2. package/dist/{apply-manager-UXUTYFZn.mjs.map → apply-manager-tRqDkzBV.mjs.map} +1 -1
  3. package/dist/{branch-lifecycle-Dd84lx37.mjs → branch-lifecycle-BAfgSQBv.mjs} +2 -2
  4. package/dist/{branch-lifecycle-Dd84lx37.mjs.map → branch-lifecycle-BAfgSQBv.mjs.map} +1 -1
  5. package/dist/{config-DlFcTxFG.mjs → config-oxxgznz7.mjs} +1 -1
  6. package/dist/{config-DlFcTxFG.mjs.map → config-oxxgznz7.mjs.map} +1 -1
  7. package/dist/{content-manager-DcmNwvmq.d.mts → content-manager-BgxF3dHu.d.mts} +3 -1
  8. package/dist/content-manager-BgxF3dHu.d.mts.map +1 -0
  9. package/dist/{context-CYN__o3Q.mjs → context-CrS-IvVm.mjs} +3 -3
  10. package/dist/{context-CYN__o3Q.mjs.map → context-CrS-IvVm.mjs.map} +1 -1
  11. package/dist/core/apply-manager.mjs +7 -7
  12. package/dist/core/config.mjs +1 -1
  13. package/dist/core/content-manager.d.mts +1 -1
  14. package/dist/core/content-manager.mjs +2 -2
  15. package/dist/core/context.mjs +4 -4
  16. package/dist/core/doctor.d.mts.map +1 -1
  17. package/dist/core/doctor.mjs +5 -5
  18. package/dist/core/meta-manager.d.mts +29 -1
  19. package/dist/core/meta-manager.d.mts.map +1 -1
  20. package/dist/core/meta-manager.mjs +2 -2
  21. package/dist/core/model-manager.mjs +2 -2
  22. package/dist/core/ops/index.d.mts +17 -2
  23. package/dist/core/ops/index.d.mts.map +1 -1
  24. package/dist/core/ops/index.mjs +4 -2
  25. package/dist/core/validator/index.d.mts.map +1 -1
  26. package/dist/core/validator/index.mjs +4 -4
  27. package/dist/{doctor-BwSJW1sS.mjs → doctor-BRPXxKhx.mjs} +37 -11
  28. package/dist/doctor-BRPXxKhx.mjs.map +1 -0
  29. package/dist/git/branch-lifecycle.mjs +2 -2
  30. package/dist/git/transaction.mjs +6 -6
  31. package/dist/index.mjs +12 -12
  32. package/dist/{local-BIE23GuI.mjs → local-DqmDeNxI.mjs} +6 -6
  33. package/dist/{local-BIE23GuI.mjs.map → local-DqmDeNxI.mjs.map} +1 -1
  34. package/dist/meta-manager-CJUiTgP2.mjs +98 -0
  35. package/dist/meta-manager-CJUiTgP2.mjs.map +1 -0
  36. package/dist/{model-manager-BhLsUgaB.mjs → model-manager-SiesTJrS.mjs} +48 -24
  37. package/dist/model-manager-SiesTJrS.mjs.map +1 -0
  38. package/dist/{ops-Dh2T3u53.mjs → ops-B422KP_S.mjs} +26 -23
  39. package/dist/ops-B422KP_S.mjs.map +1 -0
  40. package/dist/{paths-CYl5vCBD.mjs → paths-CmVw5Cw2.mjs} +15 -4
  41. package/dist/{paths-CYl5vCBD.mjs.map → paths-CmVw5Cw2.mjs.map} +1 -1
  42. package/dist/providers/local/index.mjs +8 -8
  43. package/dist/server/http/index.mjs +12 -12
  44. package/dist/{server-BkDuFvVt.mjs → server-DSEl2wzT.mjs} +113 -54
  45. package/dist/server-DSEl2wzT.mjs.map +1 -0
  46. package/dist/server.mjs +12 -12
  47. package/dist/{transaction-DhVoGigz.mjs → transaction-BHOsiEn1.mjs} +4 -4
  48. package/dist/{transaction-DhVoGigz.mjs.map → transaction-BHOsiEn1.mjs.map} +1 -1
  49. package/dist/{validator-8vaPxSba.mjs → validator-Ck0puppQ.mjs} +48 -8
  50. package/dist/validator-Ck0puppQ.mjs.map +1 -0
  51. package/package.json +1 -1
  52. package/dist/content-manager-DcmNwvmq.d.mts.map +0 -1
  53. package/dist/doctor-BwSJW1sS.mjs.map +0 -1
  54. package/dist/meta-manager-C900rEyG.mjs +0 -53
  55. package/dist/meta-manager-C900rEyG.mjs.map +0 -1
  56. package/dist/model-manager-BhLsUgaB.mjs.map +0 -1
  57. package/dist/ops-Dh2T3u53.mjs.map +0 -1
  58. package/dist/server-BkDuFvVt.mjs.map +0 -1
  59. package/dist/validator-8vaPxSba.mjs.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import "../serialization-B1CEzR4H.mjs";
2
2
  import "../fs-DLbVB-Ek.mjs";
3
- import "../config-DlFcTxFG.mjs";
4
- import "../model-manager-BhLsUgaB.mjs";
5
- import "../meta-manager-C900rEyG.mjs";
6
- import "../context-CYN__o3Q.mjs";
7
- import "../branch-lifecycle-Dd84lx37.mjs";
3
+ import "../meta-manager-CJUiTgP2.mjs";
4
+ import "../config-oxxgznz7.mjs";
5
+ import "../model-manager-SiesTJrS.mjs";
6
+ import "../context-CrS-IvVm.mjs";
7
+ import "../branch-lifecycle-BAfgSQBv.mjs";
8
8
  import "../id-DV_T9Ic8.mjs";
9
- import "../transaction-DhVoGigz.mjs";
10
- import { a as detectFileFramework, c as validatePatchPath, i as checkSyntax, n as applyExtract, o as replaceInLine, r as applyReuse, s as validateFrameworkExpression, t as PATCHABLE_EXTENSIONS } from "../apply-manager-UXUTYFZn.mjs";
9
+ import "../transaction-BHOsiEn1.mjs";
10
+ import { a as detectFileFramework, c as validatePatchPath, i as checkSyntax, n as applyExtract, o as replaceInLine, r as applyReuse, s as validateFrameworkExpression, t as PATCHABLE_EXTENSIONS } from "../apply-manager-tRqDkzBV.mjs";
11
11
  export { PATCHABLE_EXTENSIONS, applyExtract, applyReuse, checkSyntax, detectFileFramework, replaceInLine, validateFrameworkExpression, validatePatchPath };
@@ -1,3 +1,3 @@
1
1
  import "../fs-DLbVB-Ek.mjs";
2
- import { n as readVocabulary, t as readConfig } from "../config-DlFcTxFG.mjs";
2
+ import { n as readVocabulary, t as readConfig } from "../config-oxxgznz7.mjs";
3
3
  export { readConfig, readVocabulary };
@@ -1,2 +1,2 @@
1
- import { _ as writeContent, a as deleteContent, c as readContent, d as resolveLocaleStrategy, f as resolveMdFilePath, g as validateSlug, h as validateLocale, i as WriteResult, l as resolveContentDir, m as validateEntryId, n as DeleteOpts, o as listContent, p as serializeFrontmatter, r as ListOpts, s as parseFrontmatter, t as ContentEntry, u as resolveJsonFilePath } from "../content-manager-DcmNwvmq.mjs";
1
+ import { _ as writeContent, a as deleteContent, c as readContent, d as resolveLocaleStrategy, f as resolveMdFilePath, g as validateSlug, h as validateLocale, i as WriteResult, l as resolveContentDir, m as validateEntryId, n as DeleteOpts, o as listContent, p as serializeFrontmatter, r as ListOpts, s as parseFrontmatter, t as ContentEntry, u as resolveJsonFilePath } from "../content-manager-BgxF3dHu.mjs";
2
2
  export { ContentEntry, DeleteOpts, ListOpts, WriteResult, deleteContent, listContent, parseFrontmatter, readContent, resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy, resolveMdFilePath, serializeFrontmatter, validateEntryId, validateLocale, validateSlug, writeContent };
@@ -1,4 +1,4 @@
1
1
  import "../fs-DLbVB-Ek.mjs";
2
- import { S as writeContent, _ as resolveMdFilePath, b as validateLocale, d as listContent, f as parseFrontmatter, g as resolveLocaleStrategy, h as resolveJsonFilePath, m as resolveContentDir, p as readContent, u as deleteContent, v as serializeFrontmatter, x as validateSlug, y as validateEntryId } from "../model-manager-BhLsUgaB.mjs";
3
- import "../meta-manager-C900rEyG.mjs";
2
+ import "../meta-manager-CJUiTgP2.mjs";
3
+ import { S as writeContent, _ as resolveMdFilePath, b as validateLocale, d as listContent, f as parseFrontmatter, g as resolveLocaleStrategy, h as resolveJsonFilePath, m as resolveContentDir, p as readContent, u as deleteContent, v as serializeFrontmatter, x as validateSlug, y as validateEntryId } from "../model-manager-SiesTJrS.mjs";
4
4
  export { deleteContent, listContent, parseFrontmatter, readContent, resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy, resolveMdFilePath, serializeFrontmatter, validateEntryId, validateLocale, validateSlug, writeContent };
@@ -1,7 +1,7 @@
1
1
  import "../serialization-B1CEzR4H.mjs";
2
2
  import "../fs-DLbVB-Ek.mjs";
3
- import "../config-DlFcTxFG.mjs";
4
- import "../model-manager-BhLsUgaB.mjs";
5
- import "../meta-manager-C900rEyG.mjs";
6
- import { n as readContext, r as writeContext, t as buildContextChange } from "../context-CYN__o3Q.mjs";
3
+ import "../meta-manager-CJUiTgP2.mjs";
4
+ import "../config-oxxgznz7.mjs";
5
+ import "../model-manager-SiesTJrS.mjs";
6
+ import { n as readContext, r as writeContext, t as buildContextChange } from "../context-CrS-IvVm.mjs";
7
7
  export { buildContextChange, readContext, writeContext };
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.mts","names":[],"sources":["../../src/core/doctor.ts"],"mappings":";;AAiCA;;;;;AAEA;;;;;;;;;;;AAaA;;;;KAfY,aAAA;AAAA,UAEK,WAAA;EACf,IAAA;EACA,IAAA;EACA,MAAA;EAcM;AAGR;;;;;EAVE,QAAA,GAAW,aAAA;AAAA;AAAA,UAGI,cAAA;EACf,KAAA;EACA,IAAA;EACA,GAAA;EACA,MAAA;AAAA;AAAA,UAGe,mBAAA;EACf,KAAA;EACA,MAAA;EACA,KAAA;EACA,IAAA;AAAA;AAAA,UAGe,kBAAA;EACf,KAAA;EACA,GAAA;EACA,SAAA;AAAA;AAAA,UAGe,mBAAA;EACf,UAAA,EAAY,cAAA;EACZ,eAAA,EAAiB,mBAAA;EACjB,iBAAA,EAAmB,kBAAA;AAAA;AAAA,UAGJ,YAAA;EACf,MAAA,EAAQ,WAAA;EACR,OAAA;IACE,KAAA;IACA,MAAA;IACA,MAAA;IACA,QAAA;EAAA;;EAGF,KAAA,GAAQ,mBAAA;AAAA;AAAA,UAGO,gBAAA;EAVf;EAYA,KAAA;AAAA;AAAA,iBAGoB,SAAA,CACpB,WAAA,UACA,OAAA,GAAS,gBAAA,GACR,OAAA,CAAQ,YAAA"}
1
+ {"version":3,"file":"doctor.d.mts","names":[],"sources":["../../src/core/doctor.ts"],"mappings":";;AAkCA;;;;;AAEA;;;;;;;;;;;AAaA;;;;KAfY,aAAA;AAAA,UAEK,WAAA;EACf,IAAA;EACA,IAAA;EACA,MAAA;EAcM;AAGR;;;;;EAVE,QAAA,GAAW,aAAA;AAAA;AAAA,UAGI,cAAA;EACf,KAAA;EACA,IAAA;EACA,GAAA;EACA,MAAA;AAAA;AAAA,UAGe,mBAAA;EACf,KAAA;EACA,MAAA;EACA,KAAA;EACA,IAAA;AAAA;AAAA,UAGe,kBAAA;EACf,KAAA;EACA,GAAA;EACA,SAAA;AAAA;AAAA,UAGe,mBAAA;EACf,UAAA,EAAY,cAAA;EACZ,eAAA,EAAiB,mBAAA;EACjB,iBAAA,EAAmB,kBAAA;AAAA;AAAA,UAGJ,YAAA;EACf,MAAA,EAAQ,WAAA;EACR,OAAA;IACE,KAAA;IACA,MAAA;IACA,MAAA;IACA,QAAA;EAAA;;EAGF,KAAA,GAAQ,mBAAA;AAAA;AAAA,UAGO,gBAAA;EAVf;EAYA,KAAA;AAAA;AAAA,iBAGoB,SAAA,CACpB,WAAA,UACA,OAAA,GAAS,gBAAA,GACR,OAAA,CAAQ,YAAA"}
@@ -1,8 +1,8 @@
1
1
  import "../fs-DLbVB-Ek.mjs";
2
- import "../config-DlFcTxFG.mjs";
3
- import "../model-manager-BhLsUgaB.mjs";
4
- import "../meta-manager-C900rEyG.mjs";
5
- import "../branch-lifecycle-Dd84lx37.mjs";
2
+ import "../meta-manager-CJUiTgP2.mjs";
3
+ import "../config-oxxgznz7.mjs";
4
+ import "../model-manager-SiesTJrS.mjs";
5
+ import "../branch-lifecycle-BAfgSQBv.mjs";
6
6
  import "../scan-config-BGUflS8t.mjs";
7
- import { t as runDoctor } from "../doctor-BwSJW1sS.mjs";
7
+ import { t as runDoctor } from "../doctor-BRPXxKhx.mjs";
8
8
  export { runDoctor };
@@ -5,10 +5,38 @@ interface MetaOpts {
5
5
  locale?: string;
6
6
  entryId?: string;
7
7
  slug?: string;
8
+ /**
9
+ * `config.locales.default`. Required: it places a non-i18n model's single
10
+ * meta record, and stands in for an omitted locale on i18n models — the old
11
+ * hardcoded `'en'` fallback silently wrote to the wrong file on projects
12
+ * whose default was anything else.
13
+ */
14
+ defaultLocale: string;
8
15
  }
9
16
  declare function readMeta(projectRoot: string, model: ModelDefinition, opts: MetaOpts): Promise<EntryMeta | Record<string, EntryMeta> | null>;
17
+ /**
18
+ * Build the meta for a content write, merged over the entry's existing meta.
19
+ *
20
+ * `status` is a publish decision and belongs to whoever made it — editing a
21
+ * field must not unpublish an entry. Only an entry with no meta yet starts at
22
+ * `draft`. `source`/`updated_by` describe *this* write, so they are stamped
23
+ * every time; `approved_by`/`version` survive via the spread.
24
+ *
25
+ * Pass `existing: undefined` to mint meta for a genuinely new entry.
26
+ */
27
+ declare function mergeEntryMeta(existing: EntryMeta | undefined, data?: Record<string, unknown>): EntryMeta;
10
28
  declare function writeMeta(projectRoot: string, model: ModelDefinition, opts: MetaOpts, meta: EntryMeta): Promise<void>;
29
+ /**
30
+ * Apply many entry-meta updates to one collection locale file in a single
31
+ * read-modify-write, returning the entry IDs actually written.
32
+ *
33
+ * Do NOT loop `writeMeta` over entry IDs: every call rewrites the whole shared
34
+ * `{locale}.json`, so concurrent calls each start from the same snapshot and
35
+ * all but the last-settling one are silently lost. This function exists so that
36
+ * race cannot be reintroduced.
37
+ */
38
+ declare function writeMetaEntries(projectRoot: string, model: ModelDefinition, opts: MetaOpts, updates: Record<string, EntryMeta>): Promise<string[]>;
11
39
  declare function deleteMeta(projectRoot: string, model: ModelDefinition, opts: MetaOpts): Promise<void>;
12
40
  //#endregion
13
- export { deleteMeta, readMeta, writeMeta };
41
+ export { deleteMeta, mergeEntryMeta, readMeta, writeMeta, writeMetaEntries };
14
42
  //# sourceMappingURL=meta-manager.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"meta-manager.d.mts","names":[],"sources":["../../src/core/meta-manager.ts"],"mappings":";;;UAKU,QAAA;EACR,MAAA;EACA,OAAA;EACA,IAAA;AAAA;AAAA,iBAaoB,QAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,GACL,OAAA,CAAQ,SAAA,GAAY,MAAA,SAAe,SAAA;AAAA,iBAIhB,SAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,IAAA,EAAM,SAAA,GACL,OAAA;AAAA,iBAYmB,UAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,GACL,OAAA"}
1
+ {"version":3,"file":"meta-manager.d.mts","names":[],"sources":["../../src/core/meta-manager.ts"],"mappings":";;;UAKU,QAAA;EACR,MAAA;EACA,OAAA;EACA,IAAA;;;;;;;EAOA,aAAA;AAAA;AAAA,iBAkBoB,QAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,GACL,OAAA,CAAQ,SAAA,GAAY,MAAA,SAAe,SAAA;;;;;;;;;;;iBActB,cAAA,CACd,QAAA,EAAU,SAAA,cACV,IAAA,GAAO,MAAA,oBACN,SAAA;AAAA,iBAYmB,SAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,IAAA,EAAM,SAAA,GACL,OAAA;;;;;;;;;;iBAqBmB,gBAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,OAAA,EAAS,MAAA,SAAe,SAAA,IACvB,OAAA;AAAA,iBAUmB,UAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,GACL,OAAA"}
@@ -1,3 +1,3 @@
1
1
  import "../fs-DLbVB-Ek.mjs";
2
- import { n as readMeta, r as writeMeta, t as deleteMeta } from "../meta-manager-C900rEyG.mjs";
3
- export { deleteMeta, readMeta, writeMeta };
2
+ import { a as writeMetaEntries, i as writeMeta, n as mergeEntryMeta, r as readMeta, t as deleteMeta } from "../meta-manager-CJUiTgP2.mjs";
3
+ export { deleteMeta, mergeEntryMeta, readMeta, writeMeta, writeMetaEntries };
@@ -1,4 +1,4 @@
1
1
  import "../fs-DLbVB-Ek.mjs";
2
- import { a as fieldDefZodSchema, c as validateModelDefinition, i as deleteModel, l as writeModel, n as checkReferences, o as listModels, r as countEntries, s as readModel, t as FIELD_TYPE_ENUM } from "../model-manager-BhLsUgaB.mjs";
3
- import "../meta-manager-C900rEyG.mjs";
2
+ import "../meta-manager-CJUiTgP2.mjs";
3
+ import { a as fieldDefZodSchema, c as validateModelDefinition, i as deleteModel, l as writeModel, n as checkReferences, o as listModels, r as countEntries, s as readModel, t as FIELD_TYPE_ENUM } from "../model-manager-SiesTJrS.mjs";
4
4
  export { FIELD_TYPE_ENUM, checkReferences, countEntries, deleteModel, fieldDefZodSchema, listModels, readModel, validateModelDefinition, writeModel };
@@ -1,5 +1,5 @@
1
1
  import { _ as RepoReader, a as FileChange } from "../../index-DDX-qYNw.mjs";
2
- import { t as ContentEntry } from "../../content-manager-DcmNwvmq.mjs";
2
+ import { t as ContentEntry } from "../../content-manager-BgxF3dHu.mjs";
3
3
  import { ContentrainConfig, ModelDefinition, Vocabulary } from "@contentrain/types";
4
4
 
5
5
  //#region src/core/ops/apply-to-worktree.d.ts
@@ -52,6 +52,11 @@ interface ContentDeleteInput {
52
52
  slug?: string;
53
53
  locale?: string;
54
54
  keys?: string[];
55
+ /**
56
+ * `config.locales.default` — where a non-i18n model's single meta record
57
+ * lives. Required to resolve meta paths; see `metaFilePath`.
58
+ */
59
+ defaultLocale: string;
55
60
  }
56
61
  type ContentDeletePlan = OpPlan<string[]>;
57
62
  /**
@@ -138,7 +143,17 @@ declare function planModelSave(reader: RepoReader, input: ModelSaveInput): Promi
138
143
  //#region src/core/ops/paths.d.ts
139
144
  declare function contentFilePath(model: Pick<ModelDefinition, 'id' | 'kind' | 'domain' | 'i18n' | 'content_path' | 'locale_strategy'>, locale: string): string;
140
145
  declare function documentFilePath(model: Pick<ModelDefinition, 'id' | 'domain' | 'i18n' | 'content_path' | 'locale_strategy'>, locale: string, slug: string): string;
141
- declare function metaFilePath(model: Pick<ModelDefinition, 'id' | 'kind'>, locale: string, slug?: string): string;
146
+ /**
147
+ * Content-root-relative meta path.
148
+ *
149
+ * `i18n` is in the Pick and `defaultLocale` is required on purpose. A non-i18n
150
+ * model keeps all its content in one `data.json`, so it has exactly one meta
151
+ * record — pinned to the default locale. Passing the caller's locale straight
152
+ * through used to fan meta out across locales for that single content file
153
+ * (`meta/{id}/tr.json` *and* `meta/{id}/en.json` for one `data.json`), leaving
154
+ * every reader to guess which file was authoritative.
155
+ */
156
+ declare function metaFilePath(model: Pick<ModelDefinition, 'id' | 'kind' | 'i18n'>, locale: string, defaultLocale: string, slug?: string): string;
142
157
  //#endregion
143
158
  export { type ContentDeleteInput, type ContentDeletePlan, type ContentSaveEntryResult, type ContentSavePlan, type ModelDeleteInput, type ModelDeletePlan, type ModelSaveInput, type ModelSavePlan, type ModelSaveResult, type OpPlan, applyChangesToWorktree, contentFilePath, documentFilePath, metaFilePath, planContentDelete, planContentSave, planModelDelete, planModelSave };
144
159
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/ops/apply-to-worktree.ts","../../../src/core/ops/types.ts","../../../src/core/ops/content-delete.ts","../../../src/core/ops/content-save.ts","../../../src/core/ops/model-delete.ts","../../../src/core/ops/model-save.ts","../../../src/core/ops/paths.ts"],"mappings":";;;;;;;;;AAkBA;;;;;;;;;;iBAAsB,sBAAA,CAAuB,QAAA,UAAkB,OAAA,WAAkB,UAAA,KAAe,OAAA;;;;;;;AAAhG;;;;UCRiB,MAAA;EDQgE;ECN/E,OAAA,EAAS,UAAA;EDMqF;ECJ9F,MAAA,EAAQ,OAAA;EDI6F;ECFrG,UAAA;AAAA;AAAA,UAGe,sBAAA;EACf,MAAA;EACA,EAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,KAGU,eAAA,GAAkB,MAAA,CAAO,sBAAA;;;UCpBpB,kBAAA;EACf,KAAA,EAAO,eAAA;EACP,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,MAAA;;;;;;;;ADLhC;;;;;;iBC+CsB,iBAAA,CACpB,MAAA,EAAQ,UAAA,EACR,KAAA,EAAO,kBAAA,GACN,OAAA,CAAQ,iBAAA;;;UCnDD,SAAA;EACR,KAAA,EAAO,eAAA;EACP,OAAA,EAAS,YAAA;EACT,MAAA,EAAQ,iBAAA;EACR,UAAA,GAAa,UAAA;EHK8B;;;;;;EGE3C,YAAA;AAAA;;AFVF;;;;;;;;iBEsBsB,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,SAAA,GAAY,OAAA,CAAQ,eAAA;;;UC5BpE,gBAAA;EACf,KAAA,EAAO,eAAA;AAAA;AAAA,KAGG,eAAA,GAAkB,MAAA;;;;;;;;;;;;AHE9B;;;iBGcsB,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,gBAAA,GAAmB,OAAA,CAAQ,eAAA;;;UCN3E,cAAA;EACf,KAAA,EAAO,eAAA;AAAA;AAAA,UAGQ,eAAA;EACf,MAAA;EACA,EAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,MAAA,CAAO,eAAA;;;;;;;;AJjBnC;;iBI4BsB,aAAA,CAAc,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,cAAA,GAAiB,OAAA,CAAQ,aAAA;;;iBCPxE,eAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,2EACZ,MAAA;AAAA,iBAac,gBAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,kEACZ,MAAA,UACA,IAAA;AAAA,iBAac,YAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,kBACZ,MAAA,UACA,IAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/ops/apply-to-worktree.ts","../../../src/core/ops/types.ts","../../../src/core/ops/content-delete.ts","../../../src/core/ops/content-save.ts","../../../src/core/ops/model-delete.ts","../../../src/core/ops/model-save.ts","../../../src/core/ops/paths.ts"],"mappings":";;;;;;;;;AAkBA;;;;;;;;;;iBAAsB,sBAAA,CAAuB,QAAA,UAAkB,OAAA,WAAkB,UAAA,KAAe,OAAA;;;;;;;AAAhG;;;;UCRiB,MAAA;EDQgE;ECN/E,OAAA,EAAS,UAAA;EDMqF;ECJ9F,MAAA,EAAQ,OAAA;EDI6F;ECFrG,UAAA;AAAA;AAAA,UAGe,sBAAA;EACf,MAAA;EACA,EAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,KAGU,eAAA,GAAkB,MAAA,CAAO,sBAAA;;;UCpBpB,kBAAA;EACf,KAAA,EAAO,eAAA;EACP,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EFM2C;;;;EED3C,aAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,MAAA;;;ADVhC;;;;;;;;;;;iBCoDsB,iBAAA,CACpB,MAAA,EAAQ,UAAA,EACR,KAAA,EAAO,kBAAA,GACN,OAAA,CAAQ,iBAAA;;;UCvDD,SAAA;EACR,KAAA,EAAO,eAAA;EACP,OAAA,EAAS,YAAA;EACT,MAAA,EAAQ,iBAAA;EACR,UAAA,GAAa,UAAA;EHI8B;;;;;;EGG3C,YAAA;AAAA;;AFXF;;;;;;;;iBEuBsB,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,SAAA,GAAY,OAAA,CAAQ,eAAA;;;UC7BpE,gBAAA;EACf,KAAA,EAAO,eAAA;AAAA;AAAA,KAGG,eAAA,GAAkB,MAAA;;;;;;;;;;;;AHE9B;;;iBGcsB,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,gBAAA,GAAmB,OAAA,CAAQ,eAAA;;;UCN3E,cAAA;EACf,KAAA,EAAO,eAAA;AAAA;AAAA,UAGQ,eAAA;EACf,MAAA;EACA,EAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,MAAA,CAAO,eAAA;;;;;;;;AJjBnC;;iBI4BsB,aAAA,CAAc,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,cAAA,GAAiB,OAAA,CAAQ,aAAA;;;iBCPxE,eAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,2EACZ,MAAA;AAAA,iBAac,gBAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,kEACZ,MAAA,UACA,IAAA;;;;;;;;;;AL9BF;iBKqDgB,YAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,2BACZ,MAAA,UACA,aAAA,UACA,IAAA"}
@@ -1,4 +1,6 @@
1
- import { a as applyChangesToWorktree, i as planContentDelete, n as planModelDelete, r as planContentSave, t as planModelSave } from "../../ops-Dh2T3u53.mjs";
1
+ import { a as applyChangesToWorktree, i as planContentDelete, n as planModelDelete, r as planContentSave, t as planModelSave } from "../../ops-B422KP_S.mjs";
2
2
  import "../../serialization-B1CEzR4H.mjs";
3
- import { i as metaFilePath, n as contentFilePath, r as documentFilePath } from "../../paths-CYl5vCBD.mjs";
3
+ import { i as metaFilePath, n as contentFilePath, r as documentFilePath } from "../../paths-CmVw5Cw2.mjs";
4
+ import "../../fs-DLbVB-Ek.mjs";
5
+ import "../../meta-manager-CJUiTgP2.mjs";
4
6
  export { applyChangesToWorktree, contentFilePath, documentFilePath, metaFilePath, planContentDelete, planContentSave, planModelDelete, planModelSave };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/validator/entry.ts","../../../src/core/validator/relation-integrity.ts","../../../src/core/validator/project.ts","../../../src/core/validator/schedule.ts"],"mappings":";;;;;;;AAUA;;;;UAAiB,iBAAA;EAMN;EAJT,UAAA,GAAa,MAAA,SAAe,MAAA;EAIJ;EAFxB,cAAA;EAFa;EAIb,MAAA,GAAS,eAAA;AAAA;;;;;AAgBX;;;;;;;;;iBAAgB,eAAA,CACd,IAAA,EAAM,MAAA,mBACN,MAAA,EAAQ,MAAA,SAAe,QAAA,GACvB,OAAA,UACA,MAAA,UACA,OAAA,WACA,GAAA,GAAM,iBAAA,GACL,gBAAA;;;;;;AA7BH;;;;;;;;;UCIiB,cAAA;EACf,MAAA;EACA,OAAA,GAAU,MAAA;AAAA;;KAIA,iBAAA,IACV,aAAA,UACA,YAAA,aACG,OAAA,CAAQ,MAAA;AAAA,UAEI,6BAAA;EDOD;ECLd,QAAA;;;;;;EAMA,aAAA,IACE,aAAA,UACA,YAAA,aACG,OAAA,CAAQ,cAAA;AAAA;;;;;;;;;;;;iBAcO,sBAAA,CACpB,IAAA,EAAM,MAAA,mBACN,MAAA,EAAQ,MAAA,SAAe,QAAA,GACvB,OAAA,UACA,MAAA,UACA,OAAA,sBACA,WAAA,EAAa,iBAAA,EACb,IAAA,GAAM,6BAAA,GACL,OAAA,CAAQ,eAAA;;;UCxCM,eAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,UAGe,cAAA;EACf,KAAA;EACA,OAAA;IACE,MAAA;IACA,QAAA;IACA,OAAA;IACA,cAAA;IACA,eAAA;EAAA;EAEF,MAAA,EAAQ,eAAA;EACR,KAAA;AAAA;;;AFDF;;;;;;;;;;;;;iBEwsBsB,eAAA,CAAgB,WAAA,UAAqB,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,cAAA;AAAA,iBACzE,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,cAAA;;;;;;AF/tB9F;;;;;iBGAgB,sBAAA,CACd,IAAA,EAAM,SAAA,EACN,GAAA;EAAO,KAAA;EAAe,MAAA;EAAgB,KAAA;EAAgB,IAAA;AAAA,GACtD,MAAA,EAAQ,eAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/validator/entry.ts","../../../src/core/validator/relation-integrity.ts","../../../src/core/validator/project.ts","../../../src/core/validator/schedule.ts"],"mappings":";;;;;;;AAUA;;;;UAAiB,iBAAA;EAMN;EAJT,UAAA,GAAa,MAAA,SAAe,MAAA;EAIJ;EAFxB,cAAA;EAFa;EAIb,MAAA,GAAS,eAAA;AAAA;;;;;AAgBX;;;;;;;;;iBAAgB,eAAA,CACd,IAAA,EAAM,MAAA,mBACN,MAAA,EAAQ,MAAA,SAAe,QAAA,GACvB,OAAA,UACA,MAAA,UACA,OAAA,WACA,GAAA,GAAM,iBAAA,GACL,gBAAA;;;;;;AA7BH;;;;;;;;;UCIiB,cAAA;EACf,MAAA;EACA,OAAA,GAAU,MAAA;AAAA;;KAIA,iBAAA,IACV,aAAA,UACA,YAAA,aACG,OAAA,CAAQ,MAAA;AAAA,UAEI,6BAAA;EDOD;ECLd,QAAA;;;;;;EAMA,aAAA,IACE,aAAA,UACA,YAAA,aACG,OAAA,CAAQ,cAAA;AAAA;;;;;;;;;;;;iBAcO,sBAAA,CACpB,IAAA,EAAM,MAAA,mBACN,MAAA,EAAQ,MAAA,SAAe,QAAA,GACvB,OAAA,UACA,MAAA,UACA,OAAA,sBACA,WAAA,EAAa,iBAAA,EACb,IAAA,GAAM,6BAAA,GACL,OAAA,CAAQ,eAAA;;;UCxCM,eAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,UAGe,cAAA;EACf,KAAA;EACA,OAAA;IACE,MAAA;IACA,QAAA;IACA,OAAA;IACA,cAAA;IACA,eAAA;EAAA;EAEF,MAAA,EAAQ,eAAA;EACR,KAAA;AAAA;;;AFDF;;;;;;;;;;;;;iBEywBsB,eAAA,CAAgB,WAAA,UAAqB,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,cAAA;AAAA,iBACzE,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,cAAA;;;;;;AFhyB9F;;;;;iBGAgB,sBAAA,CACd,IAAA,EAAM,SAAA,EACN,GAAA;EAAO,KAAA;EAAe,MAAA;EAAgB,KAAA;EAAgB,IAAA;AAAA,GACtD,MAAA,EAAQ,eAAA"}
@@ -1,6 +1,6 @@
1
1
  import "../../fs-DLbVB-Ek.mjs";
2
- import "../../config-DlFcTxFG.mjs";
3
- import "../../model-manager-BhLsUgaB.mjs";
4
- import "../../meta-manager-C900rEyG.mjs";
5
- import { i as validateContent, n as validateScheduleFields, r as checkRelationIntegrity, t as validateProject } from "../../validator-8vaPxSba.mjs";
2
+ import "../../meta-manager-CJUiTgP2.mjs";
3
+ import "../../config-oxxgznz7.mjs";
4
+ import "../../model-manager-SiesTJrS.mjs";
5
+ import { i as validateContent, n as validateScheduleFields, r as checkRelationIntegrity, t as validateProject } from "../../validator-Ck0puppQ.mjs";
6
6
  export { checkRelationIntegrity, validateContent, validateProject, validateScheduleFields };
@@ -1,10 +1,10 @@
1
1
  import { a as readJson, i as readDir, o as readText, r as pathExists, t as contentrainDir } from "./fs-DLbVB-Ek.mjs";
2
- import { t as readConfig } from "./config-DlFcTxFG.mjs";
3
- import { g as resolveLocaleStrategy, h as resolveJsonFilePath, m as resolveContentDir, o as listModels, s as readModel } from "./model-manager-BhLsUgaB.mjs";
4
- import { n as checkBranchHealth, s as listRemoteCrBranches } from "./branch-lifecycle-Dd84lx37.mjs";
2
+ import { t as readConfig } from "./config-oxxgznz7.mjs";
3
+ import { g as resolveLocaleStrategy, h as resolveJsonFilePath, m as resolveContentDir, o as listModels, s as readModel } from "./model-manager-SiesTJrS.mjs";
4
+ import { n as checkBranchHealth, s as listRemoteCrBranches } from "./branch-lifecycle-BAfgSQBv.mjs";
5
5
  import { i as autoDetectSourceDirs, o as discoverFiles } from "./scan-config-BGUflS8t.mjs";
6
6
  import { join } from "node:path";
7
- import { stat } from "node:fs/promises";
7
+ import { readdir, stat } from "node:fs/promises";
8
8
  import { simpleGit } from "simple-git";
9
9
  //#region src/core/doctor.ts
10
10
  async function runDoctor(projectRoot, options = {}) {
@@ -129,14 +129,21 @@ async function runDoctor(projectRoot, options = {}) {
129
129
  const clientDir = join(crDir, "client");
130
130
  const modelsDir = join(crDir, "models");
131
131
  if (await pathExists(clientDir) && await pathExists(modelsDir)) try {
132
- const [clientStat, modelsStat] = await Promise.all([stat(clientDir), stat(modelsDir)]);
133
- const fresh = clientStat.mtimeMs >= modelsStat.mtimeMs;
134
- checks.push({
132
+ const [clientMtime, modelsMtime] = await Promise.all([newestFileMtime(clientDir), newestFileMtime(modelsDir)]);
133
+ if (clientMtime === null || modelsMtime === null) checks.push({
135
134
  name: "SDK client",
136
- pass: fresh,
137
- detail: fresh ? "Up to date" : "Stale — run `contentrain generate`",
138
- severity: fresh ? void 0 : "warning"
135
+ pass: true,
136
+ detail: "Could not check"
139
137
  });
138
+ else {
139
+ const fresh = clientMtime >= modelsMtime;
140
+ checks.push({
141
+ name: "SDK client",
142
+ pass: fresh,
143
+ detail: fresh ? "Up to date" : "Stale — run `contentrain generate`",
144
+ severity: fresh ? void 0 : "warning"
145
+ });
146
+ }
140
147
  } catch {
141
148
  checks.push({
142
149
  name: "SDK client",
@@ -190,6 +197,25 @@ async function runDoctor(projectRoot, options = {}) {
190
197
  if (usage) report.usage = usage;
191
198
  return report;
192
199
  }
200
+ /**
201
+ * Newest mtime among the files under `dir`, recursively — null if it holds none.
202
+ *
203
+ * Stat the files, never the directory. A directory's mtime only moves when an
204
+ * entry is added, removed, or renamed inside it: `generate` rewrites the client
205
+ * files in place, so `.contentrain/client` never moves after the first run,
206
+ * while a selective sync recreates model files via `git checkout`, which does
207
+ * move `.contentrain/models`. Comparing the two directories therefore reported
208
+ * "stale" permanently after any model save.
209
+ */
210
+ async function newestFileMtime(dir) {
211
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
212
+ const known = (await Promise.all(entries.map(async (entry) => {
213
+ const full = join(dir, entry.name);
214
+ if (entry.isDirectory()) return newestFileMtime(full);
215
+ return stat(full).then((s) => s.mtimeMs, () => null);
216
+ }))).filter((m) => m !== null);
217
+ return known.length > 0 ? Math.max(...known) : null;
218
+ }
193
219
  async function findOrphanContent(projectRoot) {
194
220
  const crDir = contentrainDir(projectRoot);
195
221
  const models = await listModels(projectRoot);
@@ -354,4 +380,4 @@ async function analyzeMissingLocaleKeys(projectRoot, config) {
354
380
  //#endregion
355
381
  export { runDoctor as t };
356
382
 
357
- //# sourceMappingURL=doctor-BwSJW1sS.mjs.map
383
+ //# sourceMappingURL=doctor-BRPXxKhx.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-BRPXxKhx.mjs","names":[],"sources":["../src/core/doctor.ts"],"sourcesContent":["import { join } from 'node:path'\nimport { readdir, stat } from 'node:fs/promises'\nimport type { Dirent } from 'node:fs'\nimport { simpleGit } from 'simple-git'\nimport type { ContentrainConfig, ModelDefinition } from '@contentrain/types'\nimport { readConfig } from './config.js'\nimport { listModels, readModel } from './model-manager.js'\nimport { resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy } from './content-manager.js'\nimport { autoDetectSourceDirs, discoverFiles } from './scan-config.js'\nimport { checkBranchHealth, listRemoteCrBranches } from '../git/branch-lifecycle.js'\nimport { contentrainDir, pathExists, readDir, readJson, readText } from '../util/fs.js'\n\n/**\n * Doctor — project health report.\n *\n * The public entry point is `runDoctor(projectRoot, { usage? })`. It is\n * inherently local-filesystem work (Node version, git install, file\n * mtimes, orphan directory detection), so the MCP tool surface gates\n * it behind the `localWorktree` capability — same pattern as\n * `contentrain_setup` and normalize.\n *\n * The report is structured JSON so three consumers can share it:\n *\n * - The `contentrain doctor` CLI command pretty-prints the checks.\n * - The Serve UI `/api/doctor` route returns the report to the\n * Dashboard's Doctor panel.\n * - Automation (CI, Studio) gets a deterministic JSON shape it can\n * assert against.\n *\n * Usage analysis (`--usage`) is a heavier, opt-in branch — it scans\n * every source file in the repo for content-key references. Kept\n * behind the flag so the default doctor run stays fast.\n */\n\nexport type CheckSeverity = 'error' | 'warning' | 'info'\n\nexport interface DoctorCheck {\n name: string\n pass: boolean\n detail: string\n /**\n * `error` — default for failing checks. Blocks a clean bill of health.\n * `warning` — failing-but-not-blocking (e.g. pending branches above\n * threshold, stale SDK client).\n * `info` — passed check; pure informational.\n */\n severity?: CheckSeverity\n}\n\nexport interface UnusedKeyEntry {\n model: string\n kind: string\n key: string\n locale: string\n}\n\nexport interface DuplicateValueEntry {\n model: string\n locale: string\n value: string\n keys: string[]\n}\n\nexport interface MissingLocaleEntry {\n model: string\n key: string\n missingIn: string\n}\n\nexport interface DoctorUsageAnalysis {\n unusedKeys: UnusedKeyEntry[]\n duplicateValues: DuplicateValueEntry[]\n missingLocaleKeys: MissingLocaleEntry[]\n}\n\nexport interface DoctorReport {\n checks: DoctorCheck[]\n summary: {\n total: number\n passed: number\n failed: number\n warnings: number\n }\n /** Present only when `options.usage === true`. */\n usage?: DoctorUsageAnalysis\n}\n\nexport interface RunDoctorOptions {\n /** Run heavier `--usage` analysis (unused keys, duplicates, locale gaps). */\n usage?: boolean\n}\n\nexport async function runDoctor(\n projectRoot: string,\n options: RunDoctorOptions = {},\n): Promise<DoctorReport> {\n const checks: DoctorCheck[] = []\n\n // ─── 1. Git installed ───\n try {\n const git = simpleGit(projectRoot)\n const version = await git.version()\n checks.push({\n name: 'Git',\n pass: true,\n detail: `v${version.major}.${version.minor}.${version.patch}`,\n })\n } catch {\n checks.push({ name: 'Git', pass: false, detail: 'Not installed or not in PATH', severity: 'error' })\n }\n\n // ─── 2. Git repo initialized ───\n const hasGit = await pathExists(join(projectRoot, '.git'))\n checks.push({\n name: 'Git repository',\n pass: hasGit,\n detail: hasGit ? projectRoot : 'No .git directory found',\n severity: hasGit ? undefined : 'error',\n })\n\n // ─── 3. Node version ───\n const nodeVersion = process.versions.node\n const [major] = nodeVersion.split('.').map(Number)\n const nodePass = (major ?? 0) >= 22\n checks.push({\n name: 'Node.js',\n pass: nodePass,\n detail: `v${nodeVersion}${nodePass ? '' : ' (requires ≥22)'}`,\n severity: nodePass ? undefined : 'error',\n })\n\n // ─── 4. .contentrain/ structure ───\n const crDir = contentrainDir(projectRoot)\n const hasCrDir = await pathExists(crDir)\n const hasConfig = await pathExists(join(crDir, 'config.json'))\n const hasModels = await pathExists(join(crDir, 'models'))\n const hasContent = await pathExists(join(crDir, 'content'))\n const structurePass = hasCrDir && hasConfig && hasModels && hasContent\n\n checks.push({\n name: '.contentrain/ structure',\n pass: structurePass,\n detail: !hasCrDir\n ? 'Not initialized — run `contentrain init`'\n : [\n hasConfig ? null : 'missing config.json',\n hasModels ? null : 'missing models/',\n hasContent ? null : 'missing content/',\n ].filter(Boolean).join(', ') || 'OK',\n severity: structurePass ? undefined : 'error',\n })\n\n // ─── 5. Config parseable ───\n let config: ContentrainConfig | null = null\n if (hasConfig) {\n config = await readConfig(projectRoot)\n checks.push({\n name: 'Config',\n pass: config !== null,\n detail: config\n ? `stack: ${config.stack}, locales: ${config.locales.supported.join(', ')}`\n : 'Failed to parse config.json',\n severity: config ? undefined : 'error',\n })\n }\n\n // ─── 6. Models all parseable ───\n if (hasCrDir) {\n try {\n const models = await listModels(projectRoot)\n const parseResults = await Promise.all(models.map(m => readModel(projectRoot, m.id)))\n const allParseable = parseResults.every(r => r !== null)\n checks.push({\n name: 'Models',\n pass: allParseable,\n detail: `${models.length} model(s)${allParseable ? ', all valid' : ', some failed to parse'}`,\n severity: allParseable ? undefined : 'error',\n })\n } catch {\n checks.push({ name: 'Models', pass: false, detail: 'Failed to read models', severity: 'error' })\n }\n }\n\n // ─── 7. Orphan content ───\n if (hasCrDir) {\n const orphans = await findOrphanContent(projectRoot)\n checks.push({\n name: 'Orphan content',\n pass: orphans.length === 0,\n detail: orphans.length === 0 ? 'None' : `Found: ${orphans.join(', ')}`,\n severity: orphans.length === 0 ? undefined : 'warning',\n })\n }\n\n // ─── 8. Stale contentrain branches ───\n if (hasGit) {\n try {\n const health = await checkBranchHealth(projectRoot)\n checks.push({\n name: 'Pending branches',\n pass: !health.blocked && !health.warning,\n detail: health.message\n ?? (health.unmerged === 0 ? 'None' : `${health.unmerged} active cr/* branch(es)`),\n severity: health.blocked ? 'error' : health.warning ? 'warning' : undefined,\n })\n } catch {\n checks.push({ name: 'Pending branches', pass: true, detail: 'Could not check' })\n }\n\n // ─── 8b. Remote cr/* branches ───\n // Authoritative ls-remote count (local branch pressure cannot see the\n // remote pile). Best-effort: skipped entirely without a remote, and an\n // unreachable remote is informational — doctor never fails offline.\n const remoteList = await listRemoteCrBranches(projectRoot, { timeoutMs: 5000 })\n if (remoteList) {\n if (remoteList.error) {\n checks.push({\n name: 'Remote branches',\n pass: true,\n detail: `Could not check ${remoteList.remote} (offline?)`,\n severity: 'info',\n })\n } else {\n const count = remoteList.branches.length\n const warnLimit = config?.branchWarnLimit ?? 50\n const warn = count >= warnLimit\n checks.push({\n name: 'Remote branches',\n pass: !warn,\n detail: count === 0\n ? `None on ${remoteList.remote}`\n : `${count} cr/* branch(es) on ${remoteList.remote}${warn ? ' — run `contentrain prune` to remove merged leftovers' : ''}`,\n severity: warn ? 'warning' : undefined,\n })\n }\n }\n }\n\n // ─── 9. SDK client freshness ───\n const clientDir = join(crDir, 'client')\n const modelsDir = join(crDir, 'models')\n if (await pathExists(clientDir) && await pathExists(modelsDir)) {\n try {\n const [clientMtime, modelsMtime] = await Promise.all([\n newestFileMtime(clientDir),\n newestFileMtime(modelsDir),\n ])\n if (clientMtime === null || modelsMtime === null) {\n checks.push({ name: 'SDK client', pass: true, detail: 'Could not check' })\n } else {\n const fresh = clientMtime >= modelsMtime\n checks.push({\n name: 'SDK client',\n pass: fresh,\n detail: fresh ? 'Up to date' : 'Stale — run `contentrain generate`',\n severity: fresh ? undefined : 'warning',\n })\n }\n } catch {\n checks.push({ name: 'SDK client', pass: true, detail: 'Could not check' })\n }\n }\n\n // ─── 10–12. Usage analysis (optional) ───\n let usage: DoctorUsageAnalysis | undefined\n if (options.usage && hasCrDir && config) {\n const [unusedKeys, duplicateValues, missingLocaleKeys] = await Promise.all([\n analyzeUnusedKeys(projectRoot, config),\n analyzeDuplicateValues(projectRoot, config),\n analyzeMissingLocaleKeys(projectRoot, config),\n ])\n usage = { unusedKeys, duplicateValues, missingLocaleKeys }\n\n checks.push({\n name: 'Unused content keys',\n pass: unusedKeys.length === 0,\n detail: unusedKeys.length === 0\n ? 'All keys referenced in source'\n : `${unusedKeys.length} key(s) not referenced in source code`,\n severity: unusedKeys.length === 0 ? undefined : 'warning',\n })\n\n checks.push({\n name: 'Duplicate dictionary values',\n pass: duplicateValues.length === 0,\n detail: duplicateValues.length === 0\n ? 'No duplicate values'\n : `${duplicateValues.length} value(s) mapped to multiple keys`,\n severity: duplicateValues.length === 0 ? undefined : 'warning',\n })\n\n checks.push({\n name: 'Locale key coverage',\n pass: missingLocaleKeys.length === 0,\n detail: missingLocaleKeys.length === 0\n ? 'All locales have matching keys'\n : `${missingLocaleKeys.length} key(s) missing in some locales`,\n severity: missingLocaleKeys.length === 0 ? undefined : 'warning',\n })\n }\n\n const passed = checks.filter(c => c.pass).length\n const failed = checks.length - passed\n const warnings = checks.filter(c => !c.pass && c.severity === 'warning').length\n\n const report: DoctorReport = {\n checks,\n summary: { total: checks.length, passed, failed, warnings },\n }\n if (usage) report.usage = usage\n return report\n}\n\n/**\n * Newest mtime among the files under `dir`, recursively — null if it holds none.\n *\n * Stat the files, never the directory. A directory's mtime only moves when an\n * entry is added, removed, or renamed inside it: `generate` rewrites the client\n * files in place, so `.contentrain/client` never moves after the first run,\n * while a selective sync recreates model files via `git checkout`, which does\n * move `.contentrain/models`. Comparing the two directories therefore reported\n * \"stale\" permanently after any model save.\n */\nasync function newestFileMtime(dir: string): Promise<number | null> {\n const entries: Dirent[] = await readdir(dir, { withFileTypes: true }).catch(() => [])\n const mtimes = await Promise.all(entries.map(async (entry) => {\n const full = join(dir, entry.name)\n if (entry.isDirectory()) return newestFileMtime(full)\n return stat(full).then(s => s.mtimeMs, () => null)\n }))\n const known = mtimes.filter((m): m is number => m !== null)\n return known.length > 0 ? Math.max(...known) : null\n}\n\nasync function findOrphanContent(projectRoot: string): Promise<string[]> {\n const crDir = contentrainDir(projectRoot)\n const models = await listModels(projectRoot)\n const orphans: string[] = []\n\n const knownContentDirs = new Set<string>()\n for (const m of models) {\n const full = await readModel(projectRoot, m.id)\n const modelForPath = full\n ? {\n ...full,\n content_path: full.content_path ?? (m as { content_path?: string }).content_path,\n }\n : {\n id: m.id,\n name: m.id,\n kind: m.kind,\n domain: m.domain,\n i18n: m.i18n,\n fields: {},\n content_path: (m as { content_path?: string }).content_path,\n }\n knownContentDirs.add(resolveContentDir(projectRoot, modelForPath))\n }\n\n const contentDir = join(crDir, 'content')\n if (await pathExists(contentDir)) {\n const domains = await readDir(contentDir)\n for (const domain of domains) {\n const domainDir = join(contentDir, domain)\n const entries = await readDir(domainDir)\n for (const entry of entries) {\n if (entry === '.gitkeep') continue\n const entryDir = join(domainDir, entry)\n if (!knownContentDirs.has(entryDir)) {\n orphans.push(`${domain}/${entry}`)\n }\n }\n }\n }\n\n for (const dir of knownContentDirs) {\n if (dir.startsWith(contentDir)) continue\n if (!await pathExists(dir)) {\n orphans.push(`(missing custom path) ${dir}`)\n continue\n }\n await readDir(dir)\n }\n\n return orphans\n}\n\nasync function analyzeUnusedKeys(\n projectRoot: string,\n config: ContentrainConfig,\n): Promise<UnusedKeyEntry[]> {\n const sourceDirs = await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, { paths: sourceDirs })\n if (files.length === 0) return []\n\n const chunks = await Promise.all(\n files.map(async (relPath) => {\n const content = await readText(join(projectRoot, relPath))\n return content ?? ''\n }),\n )\n const allSource = chunks.join('\\n')\n\n const models = await listModels(projectRoot)\n const defaultLocale = config.locales.default\n const unused: UnusedKeyEntry[] = []\n\n for (const m of models) {\n const fullModel = await readModel(projectRoot, m.id)\n if (!fullModel) continue\n\n const keys = await extractContentKeys(projectRoot, fullModel, defaultLocale)\n for (const key of keys) {\n if (!allSource.includes(key)) {\n unused.push({ model: m.id, kind: m.kind, key, locale: defaultLocale })\n }\n }\n }\n\n return unused\n}\n\nasync function extractContentKeys(\n projectRoot: string,\n model: ModelDefinition,\n locale: string,\n): Promise<string[]> {\n const cDir = resolveContentDir(projectRoot, model)\n if (!await pathExists(cDir)) return []\n\n switch (model.kind) {\n case 'dictionary': {\n const filePath = resolveJsonFilePath(cDir, model, locale)\n const data = await readJson<Record<string, string>>(filePath)\n return data ? Object.keys(data) : []\n }\n case 'collection': {\n const filePath = resolveJsonFilePath(cDir, model, locale)\n const data = await readJson<Record<string, Record<string, unknown>>>(filePath)\n return data ? Object.keys(data) : []\n }\n case 'document': {\n const strategy = resolveLocaleStrategy(model)\n const slugs: string[] = []\n if (!model.i18n) {\n const files = await readDir(cDir)\n for (const f of files) if (f.endsWith('.md')) slugs.push(f.replace('.md', ''))\n } else if (strategy === 'file') {\n const dirs = await readDir(cDir)\n for (const d of dirs) if (!d.startsWith('.')) slugs.push(d)\n } else if (strategy === 'suffix') {\n const files = await readDir(cDir)\n const suffix = `.${locale}.md`\n for (const f of files) if (f.endsWith(suffix)) slugs.push(f.slice(0, -suffix.length))\n } else if (strategy === 'directory') {\n const localeDir = join(cDir, locale)\n if (await pathExists(localeDir)) {\n const files = await readDir(localeDir)\n for (const f of files) if (f.endsWith('.md')) slugs.push(f.replace('.md', ''))\n }\n } else {\n const files = await readDir(cDir)\n for (const f of files) if (f.endsWith('.md')) slugs.push(f.replace('.md', ''))\n }\n return slugs\n }\n case 'singleton':\n return []\n default:\n return []\n }\n}\n\nasync function analyzeDuplicateValues(\n projectRoot: string,\n config: ContentrainConfig,\n): Promise<DuplicateValueEntry[]> {\n const models = await listModels(projectRoot)\n const result: DuplicateValueEntry[] = []\n\n for (const m of models) {\n if (m.kind !== 'dictionary') continue\n const fullModel = await readModel(projectRoot, m.id)\n if (!fullModel) continue\n\n const cDir = resolveContentDir(projectRoot, fullModel)\n for (const locale of config.locales.supported) {\n const filePath = resolveJsonFilePath(cDir, fullModel, locale)\n const data = await readJson<Record<string, string>>(filePath)\n if (!data) continue\n\n const valueToKeys = new Map<string, string[]>()\n for (const [key, value] of Object.entries(data)) {\n const arr = valueToKeys.get(value)\n if (arr) arr.push(key)\n else valueToKeys.set(value, [key])\n }\n\n for (const [value, keys] of valueToKeys) {\n if (keys.length > 1) {\n result.push({ model: m.id, locale, value, keys })\n }\n }\n }\n }\n\n return result\n}\n\nasync function analyzeMissingLocaleKeys(\n projectRoot: string,\n config: ContentrainConfig,\n): Promise<MissingLocaleEntry[]> {\n if (config.locales.supported.length < 2) return []\n\n const models = await listModels(projectRoot)\n const result: MissingLocaleEntry[] = []\n const defaultLocale = config.locales.default\n const otherLocales = config.locales.supported.filter(l => l !== defaultLocale)\n\n for (const m of models) {\n if (m.kind !== 'dictionary' && m.kind !== 'collection') continue\n if (!m.i18n) continue\n\n const fullModel = await readModel(projectRoot, m.id)\n if (!fullModel) continue\n\n const cDir = resolveContentDir(projectRoot, fullModel)\n const defaultPath = resolveJsonFilePath(cDir, fullModel, defaultLocale)\n const defaultData = await readJson<Record<string, unknown>>(defaultPath)\n if (!defaultData) continue\n const defaultKeys = new Set(Object.keys(defaultData))\n\n for (const locale of otherLocales) {\n const localePath = resolveJsonFilePath(cDir, fullModel, locale)\n const localeData = await readJson<Record<string, unknown>>(localePath)\n const localeKeys = localeData ? new Set(Object.keys(localeData)) : new Set<string>()\n\n for (const key of defaultKeys) {\n if (!localeKeys.has(key)) {\n result.push({ model: m.id, key, missingIn: locale })\n }\n }\n }\n }\n\n return result\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  import "../fs-DLbVB-Ek.mjs";
2
- import "../config-DlFcTxFG.mjs";
3
- import { a as deleteRemoteBranch, c as pruneMergedRemoteBranches, i as cleanupMergedBranches, n as checkBranchHealth, o as isRefMerged, r as classifyMergedBranches, s as listRemoteCrBranches, t as branchDiff } from "../branch-lifecycle-Dd84lx37.mjs";
2
+ import "../config-oxxgznz7.mjs";
3
+ import { a as deleteRemoteBranch, c as pruneMergedRemoteBranches, i as cleanupMergedBranches, n as checkBranchHealth, o as isRefMerged, r as classifyMergedBranches, s as listRemoteCrBranches, t as branchDiff } from "../branch-lifecycle-BAfgSQBv.mjs";
4
4
  export { branchDiff, checkBranchHealth, classifyMergedBranches, cleanupMergedBranches, deleteRemoteBranch, isRefMerged, listRemoteCrBranches, pruneMergedRemoteBranches };
@@ -1,10 +1,10 @@
1
1
  import "../serialization-B1CEzR4H.mjs";
2
2
  import "../fs-DLbVB-Ek.mjs";
3
- import "../config-DlFcTxFG.mjs";
4
- import "../model-manager-BhLsUgaB.mjs";
5
- import "../meta-manager-C900rEyG.mjs";
6
- import "../context-CYN__o3Q.mjs";
7
- import "../branch-lifecycle-Dd84lx37.mjs";
3
+ import "../meta-manager-CJUiTgP2.mjs";
4
+ import "../config-oxxgznz7.mjs";
5
+ import "../model-manager-SiesTJrS.mjs";
6
+ import "../context-CrS-IvVm.mjs";
7
+ import "../branch-lifecycle-BAfgSQBv.mjs";
8
8
  import "../id-DV_T9Ic8.mjs";
9
- import { i as mergeBranch, n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "../transaction-DhVoGigz.mjs";
9
+ import { i as mergeBranch, n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "../transaction-BHOsiEn1.mjs";
10
10
  export { buildBranchName, createTransaction, ensureContentBranch, mergeBranch };
package/dist/index.mjs CHANGED
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import "./contracts-DfL0BfrD.mjs";
3
- import "./ops-Dh2T3u53.mjs";
3
+ import "./ops-B422KP_S.mjs";
4
4
  import "./serialization-B1CEzR4H.mjs";
5
5
  import "./fs-DLbVB-Ek.mjs";
6
- import "./config-DlFcTxFG.mjs";
7
- import "./model-manager-BhLsUgaB.mjs";
8
- import "./meta-manager-C900rEyG.mjs";
9
- import "./context-CYN__o3Q.mjs";
10
- import "./branch-lifecycle-Dd84lx37.mjs";
6
+ import "./meta-manager-CJUiTgP2.mjs";
7
+ import "./config-oxxgznz7.mjs";
8
+ import "./model-manager-SiesTJrS.mjs";
9
+ import "./context-CrS-IvVm.mjs";
10
+ import "./branch-lifecycle-BAfgSQBv.mjs";
11
11
  import "./id-DV_T9Ic8.mjs";
12
- import "./transaction-DhVoGigz.mjs";
13
- import "./local-BIE23GuI.mjs";
12
+ import "./transaction-BHOsiEn1.mjs";
13
+ import "./local-DqmDeNxI.mjs";
14
14
  import "./detect-wVSI9VuY.mjs";
15
15
  import "./annotations-D3tlsF38.mjs";
16
- import { n as createServer } from "./server-BkDuFvVt.mjs";
17
- import "./validator-8vaPxSba.mjs";
16
+ import { n as createServer } from "./server-DSEl2wzT.mjs";
17
+ import "./validator-Ck0puppQ.mjs";
18
18
  import "./scan-config-BGUflS8t.mjs";
19
19
  import "./graph-builder-CRUX_8mA.mjs";
20
20
  import "./scanner-VOwrKLGC.mjs";
21
21
  import "./tsx-parser-B_aI_C2r.mjs";
22
- import "./apply-manager-UXUTYFZn.mjs";
23
- import "./doctor-BwSJW1sS.mjs";
22
+ import "./apply-manager-tRqDkzBV.mjs";
23
+ import "./doctor-BRPXxKhx.mjs";
24
24
  import { resolve } from "node:path";
25
25
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
26
26
  //#region src/index.ts
@@ -1,9 +1,9 @@
1
1
  import { t as LOCAL_CAPABILITIES } from "./contracts-DfL0BfrD.mjs";
2
- import { a as applyChangesToWorktree } from "./ops-Dh2T3u53.mjs";
3
- import { t as readConfig } from "./config-DlFcTxFG.mjs";
4
- import { C as LocalReader } from "./model-manager-BhLsUgaB.mjs";
5
- import { a as deleteRemoteBranch, r as classifyMergedBranches } from "./branch-lifecycle-Dd84lx37.mjs";
6
- import { i as mergeBranch$1, n as createTransaction } from "./transaction-DhVoGigz.mjs";
2
+ import { a as applyChangesToWorktree } from "./ops-B422KP_S.mjs";
3
+ import { t as readConfig } from "./config-oxxgznz7.mjs";
4
+ import { C as LocalReader } from "./model-manager-SiesTJrS.mjs";
5
+ import { a as deleteRemoteBranch, r as classifyMergedBranches } from "./branch-lifecycle-BAfgSQBv.mjs";
6
+ import { i as mergeBranch$1, n as createTransaction } from "./transaction-BHOsiEn1.mjs";
7
7
  import { CONTENTRAIN_BRANCH } from "@contentrain/types";
8
8
  import { simpleGit } from "simple-git";
9
9
  //#region src/providers/local/branch-ops.ts
@@ -176,4 +176,4 @@ var LocalProvider = class {
176
176
  //#endregion
177
177
  export { isMerged as n, LocalProvider as t };
178
178
 
179
- //# sourceMappingURL=local-BIE23GuI.mjs.map
179
+ //# sourceMappingURL=local-DqmDeNxI.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-BIE23GuI.mjs","names":["mergeBranchOp","listBranchesOp","createBranchOp","deleteBranchOp","getBranchDiffOp","mergeBranchOp","isMergedOp","getDefaultBranchOp"],"sources":["../src/providers/local/branch-ops.ts","../src/providers/local/provider.ts"],"sourcesContent":["import { simpleGit } from 'simple-git'\nimport { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport type { Branch, FileDiff, MergeResult } from '../../core/contracts/index.js'\nimport { readConfig } from '../../core/config.js'\nimport { classifyMergedBranches, deleteRemoteBranch } from '../../git/branch-lifecycle.js'\nimport { mergeBranch as mergeBranchOp } from '../../git/transaction.js'\n\n/**\n * Branch/merge/diff helpers backed by the local simple-git worktree flow.\n *\n * Pure functions composed into `LocalProvider` — mirroring the shape of\n * `providers/github/branch-ops.ts` so the two providers share the same\n * surface at the `RepoProvider` boundary.\n */\n\nexport async function getDefaultBranch(projectRoot: string): Promise<string> {\n const config = await readConfig(projectRoot)\n if (config?.repository?.default_branch) return config.repository.default_branch\n const envBranch = process.env['CONTENTRAIN_BRANCH']\n if (envBranch) return envBranch\n const git = simpleGit(projectRoot)\n const current = (await git.raw(['branch', '--show-current'])).trim()\n return current || 'main'\n}\n\nexport async function listBranches(\n projectRoot: string,\n prefix?: string,\n): Promise<Branch[]> {\n const git = simpleGit(projectRoot)\n const summary = await git.branchLocal()\n const names = prefix\n ? summary.all.filter(n => n.startsWith(prefix))\n : summary.all\n const branches: Branch[] = []\n for (const name of names) {\n const info = summary.branches[name]\n branches.push({ name, sha: info?.commit ?? '' })\n }\n return branches\n}\n\nexport async function createBranch(\n projectRoot: string,\n name: string,\n fromRef: string,\n): Promise<void> {\n const git = simpleGit(projectRoot)\n await git.raw(['branch', name, fromRef])\n}\n\nexport async function deleteBranch(\n projectRoot: string,\n name: string,\n): Promise<void> {\n const git = simpleGit(projectRoot)\n await git.deleteLocalBranch(name, true)\n // Parity with the remote-API providers, whose deleteBranch removes the\n // remote ref: best-effort, config-gated inside the helper, never throws.\n await deleteRemoteBranch(projectRoot, name)\n}\n\nexport async function getBranchDiff(\n projectRoot: string,\n branch: string,\n base: string,\n): Promise<FileDiff[]> {\n const git = simpleGit(projectRoot)\n const raw = await git.raw(['diff', '--name-status', `${base}...${branch}`])\n const diffs: FileDiff[] = []\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue\n const [code, ...pathParts] = line.split('\\t')\n const path = pathParts[pathParts.length - 1]\n if (!code || !path) continue\n const status: FileDiff['status'] = code.startsWith('A')\n ? 'added'\n : code.startsWith('D')\n ? 'removed'\n : 'modified'\n diffs.push({ path, status, before: null, after: null })\n }\n return diffs\n}\n\nexport async function mergeBranch(\n projectRoot: string,\n branch: string,\n into: string,\n): Promise<MergeResult> {\n if (into !== CONTENTRAIN_BRANCH) {\n throw Object.assign(new Error(\n `LocalProvider.mergeBranch only supports merging into \"${CONTENTRAIN_BRANCH}\" (got \"${into}\"). `\n + `The local flow merges feature branches into the content-tracking branch and fast-forwards the base branch via update-ref.`,\n ), {\n code: 'UNSUPPORTED_MERGE_TARGET',\n agent_hint: `Pass \"${CONTENTRAIN_BRANCH}\" as the merge target, or use a non-local provider that supports arbitrary targets.`,\n developer_action: `Merge \"${branch}\" into \"${CONTENTRAIN_BRANCH}\" instead.`,\n })\n }\n const result = await mergeBranchOp(projectRoot, branch)\n return {\n merged: true,\n sha: result.commit,\n pullRequestUrl: null,\n sync: result.sync,\n ...(result.remote ? { remote: result.remote } : {}),\n }\n}\n\nexport async function isMerged(\n projectRoot: string,\n branch: string,\n into: string,\n): Promise<boolean> {\n try {\n const merged = await classifyMergedBranches(projectRoot, [branch], into)\n return merged.has(branch)\n } catch {\n return false\n }\n}\n","import { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport type {\n Branch,\n FileDiff,\n MergeResult,\n ProviderCapabilities,\n RepoProvider,\n} from '../../core/contracts/index.js'\nimport { LOCAL_CAPABILITIES } from '../../core/contracts/index.js'\nimport { applyChangesToWorktree } from '../../core/ops/index.js'\nimport { createTransaction } from '../../git/transaction.js'\nimport {\n createBranch as createBranchOp,\n deleteBranch as deleteBranchOp,\n getBranchDiff as getBranchDiffOp,\n getDefaultBranch as getDefaultBranchOp,\n isMerged as isMergedOp,\n listBranches as listBranchesOp,\n mergeBranch as mergeBranchOp,\n} from './branch-ops.js'\nimport { LocalReader } from './reader.js'\nimport type { LocalApplyPlanInput, LocalApplyResult } from './types.js'\n\nconst DEFAULT_AUTHOR_NAME = 'Contentrain'\nconst DEFAULT_AUTHOR_EMAIL = 'ai@contentrain.io'\n\n/**\n * LocalProvider — the local-filesystem, worktree-backed content provider.\n *\n * Implements the full `RepoProvider` surface:\n * - Reader methods delegate to `LocalReader`.\n * - `applyPlan` wraps `createTransaction` and returns `LocalApplyResult`\n * (a superset of `Commit` carrying workflow action + selective sync).\n * - Branch ops mirror `GitHubProvider` — thin wrappers over the local\n * simple-git helpers in `./branch-ops.ts`.\n *\n * `mergeBranch` only supports merging into the singleton\n * `CONTENTRAIN_BRANCH`; the local flow advances the base branch via\n * `update-ref` in `transaction.mergeBranch`, so arbitrary merge targets\n * would bypass that invariant.\n */\nexport class LocalProvider implements RepoProvider {\n readonly capabilities: ProviderCapabilities = LOCAL_CAPABILITIES\n private readonly reader: LocalReader\n\n constructor(public readonly projectRoot: string) {\n this.reader = new LocalReader(projectRoot)\n }\n\n readFile(path: string, ref?: string): Promise<string> {\n return this.reader.readFile(path, ref)\n }\n\n listDirectory(path: string, ref?: string): Promise<string[]> {\n return this.reader.listDirectory(path, ref)\n }\n\n fileExists(path: string, ref?: string): Promise<boolean> {\n return this.reader.fileExists(path, ref)\n }\n\n async applyPlan(input: LocalApplyPlanInput): Promise<LocalApplyResult> {\n const tx = await createTransaction(this.projectRoot, input.branch, {\n workflowOverride: input.workflowOverride,\n })\n try {\n await tx.write(async (wt) => {\n await applyChangesToWorktree(wt, input.changes)\n })\n await tx.commit(input.message, input.context)\n const gitResult = await tx.complete()\n return {\n sha: gitResult.commit,\n message: input.message,\n author: {\n name: process.env['CONTENTRAIN_AUTHOR_NAME'] ?? DEFAULT_AUTHOR_NAME,\n email: process.env['CONTENTRAIN_AUTHOR_EMAIL'] ?? DEFAULT_AUTHOR_EMAIL,\n },\n timestamp: new Date().toISOString(),\n workflowAction: gitResult.action,\n sync: gitResult.sync,\n warning: gitResult.warning,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n listBranches(prefix?: string): Promise<Branch[]> {\n return listBranchesOp(this.projectRoot, prefix)\n }\n\n async createBranch(name: string, fromRef?: string): Promise<void> {\n const resolved = fromRef ?? CONTENTRAIN_BRANCH\n await createBranchOp(this.projectRoot, name, resolved)\n }\n\n deleteBranch(name: string): Promise<void> {\n return deleteBranchOp(this.projectRoot, name)\n }\n\n getBranchDiff(branch: string, base?: string): Promise<FileDiff[]> {\n const resolved = base ?? CONTENTRAIN_BRANCH\n return getBranchDiffOp(this.projectRoot, branch, resolved)\n }\n\n mergeBranch(branch: string, into: string): Promise<MergeResult> {\n return mergeBranchOp(this.projectRoot, branch, into)\n }\n\n isMerged(branch: string, into?: string): Promise<boolean> {\n const resolved = into ?? CONTENTRAIN_BRANCH\n return isMergedOp(this.projectRoot, branch, resolved)\n }\n\n getDefaultBranch(): Promise<string> {\n return getDefaultBranchOp(this.projectRoot)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"local-DqmDeNxI.mjs","names":["mergeBranchOp","listBranchesOp","createBranchOp","deleteBranchOp","getBranchDiffOp","mergeBranchOp","isMergedOp","getDefaultBranchOp"],"sources":["../src/providers/local/branch-ops.ts","../src/providers/local/provider.ts"],"sourcesContent":["import { simpleGit } from 'simple-git'\nimport { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport type { Branch, FileDiff, MergeResult } from '../../core/contracts/index.js'\nimport { readConfig } from '../../core/config.js'\nimport { classifyMergedBranches, deleteRemoteBranch } from '../../git/branch-lifecycle.js'\nimport { mergeBranch as mergeBranchOp } from '../../git/transaction.js'\n\n/**\n * Branch/merge/diff helpers backed by the local simple-git worktree flow.\n *\n * Pure functions composed into `LocalProvider` — mirroring the shape of\n * `providers/github/branch-ops.ts` so the two providers share the same\n * surface at the `RepoProvider` boundary.\n */\n\nexport async function getDefaultBranch(projectRoot: string): Promise<string> {\n const config = await readConfig(projectRoot)\n if (config?.repository?.default_branch) return config.repository.default_branch\n const envBranch = process.env['CONTENTRAIN_BRANCH']\n if (envBranch) return envBranch\n const git = simpleGit(projectRoot)\n const current = (await git.raw(['branch', '--show-current'])).trim()\n return current || 'main'\n}\n\nexport async function listBranches(\n projectRoot: string,\n prefix?: string,\n): Promise<Branch[]> {\n const git = simpleGit(projectRoot)\n const summary = await git.branchLocal()\n const names = prefix\n ? summary.all.filter(n => n.startsWith(prefix))\n : summary.all\n const branches: Branch[] = []\n for (const name of names) {\n const info = summary.branches[name]\n branches.push({ name, sha: info?.commit ?? '' })\n }\n return branches\n}\n\nexport async function createBranch(\n projectRoot: string,\n name: string,\n fromRef: string,\n): Promise<void> {\n const git = simpleGit(projectRoot)\n await git.raw(['branch', name, fromRef])\n}\n\nexport async function deleteBranch(\n projectRoot: string,\n name: string,\n): Promise<void> {\n const git = simpleGit(projectRoot)\n await git.deleteLocalBranch(name, true)\n // Parity with the remote-API providers, whose deleteBranch removes the\n // remote ref: best-effort, config-gated inside the helper, never throws.\n await deleteRemoteBranch(projectRoot, name)\n}\n\nexport async function getBranchDiff(\n projectRoot: string,\n branch: string,\n base: string,\n): Promise<FileDiff[]> {\n const git = simpleGit(projectRoot)\n const raw = await git.raw(['diff', '--name-status', `${base}...${branch}`])\n const diffs: FileDiff[] = []\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue\n const [code, ...pathParts] = line.split('\\t')\n const path = pathParts[pathParts.length - 1]\n if (!code || !path) continue\n const status: FileDiff['status'] = code.startsWith('A')\n ? 'added'\n : code.startsWith('D')\n ? 'removed'\n : 'modified'\n diffs.push({ path, status, before: null, after: null })\n }\n return diffs\n}\n\nexport async function mergeBranch(\n projectRoot: string,\n branch: string,\n into: string,\n): Promise<MergeResult> {\n if (into !== CONTENTRAIN_BRANCH) {\n throw Object.assign(new Error(\n `LocalProvider.mergeBranch only supports merging into \"${CONTENTRAIN_BRANCH}\" (got \"${into}\"). `\n + `The local flow merges feature branches into the content-tracking branch and fast-forwards the base branch via update-ref.`,\n ), {\n code: 'UNSUPPORTED_MERGE_TARGET',\n agent_hint: `Pass \"${CONTENTRAIN_BRANCH}\" as the merge target, or use a non-local provider that supports arbitrary targets.`,\n developer_action: `Merge \"${branch}\" into \"${CONTENTRAIN_BRANCH}\" instead.`,\n })\n }\n const result = await mergeBranchOp(projectRoot, branch)\n return {\n merged: true,\n sha: result.commit,\n pullRequestUrl: null,\n sync: result.sync,\n ...(result.remote ? { remote: result.remote } : {}),\n }\n}\n\nexport async function isMerged(\n projectRoot: string,\n branch: string,\n into: string,\n): Promise<boolean> {\n try {\n const merged = await classifyMergedBranches(projectRoot, [branch], into)\n return merged.has(branch)\n } catch {\n return false\n }\n}\n","import { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport type {\n Branch,\n FileDiff,\n MergeResult,\n ProviderCapabilities,\n RepoProvider,\n} from '../../core/contracts/index.js'\nimport { LOCAL_CAPABILITIES } from '../../core/contracts/index.js'\nimport { applyChangesToWorktree } from '../../core/ops/index.js'\nimport { createTransaction } from '../../git/transaction.js'\nimport {\n createBranch as createBranchOp,\n deleteBranch as deleteBranchOp,\n getBranchDiff as getBranchDiffOp,\n getDefaultBranch as getDefaultBranchOp,\n isMerged as isMergedOp,\n listBranches as listBranchesOp,\n mergeBranch as mergeBranchOp,\n} from './branch-ops.js'\nimport { LocalReader } from './reader.js'\nimport type { LocalApplyPlanInput, LocalApplyResult } from './types.js'\n\nconst DEFAULT_AUTHOR_NAME = 'Contentrain'\nconst DEFAULT_AUTHOR_EMAIL = 'ai@contentrain.io'\n\n/**\n * LocalProvider — the local-filesystem, worktree-backed content provider.\n *\n * Implements the full `RepoProvider` surface:\n * - Reader methods delegate to `LocalReader`.\n * - `applyPlan` wraps `createTransaction` and returns `LocalApplyResult`\n * (a superset of `Commit` carrying workflow action + selective sync).\n * - Branch ops mirror `GitHubProvider` — thin wrappers over the local\n * simple-git helpers in `./branch-ops.ts`.\n *\n * `mergeBranch` only supports merging into the singleton\n * `CONTENTRAIN_BRANCH`; the local flow advances the base branch via\n * `update-ref` in `transaction.mergeBranch`, so arbitrary merge targets\n * would bypass that invariant.\n */\nexport class LocalProvider implements RepoProvider {\n readonly capabilities: ProviderCapabilities = LOCAL_CAPABILITIES\n private readonly reader: LocalReader\n\n constructor(public readonly projectRoot: string) {\n this.reader = new LocalReader(projectRoot)\n }\n\n readFile(path: string, ref?: string): Promise<string> {\n return this.reader.readFile(path, ref)\n }\n\n listDirectory(path: string, ref?: string): Promise<string[]> {\n return this.reader.listDirectory(path, ref)\n }\n\n fileExists(path: string, ref?: string): Promise<boolean> {\n return this.reader.fileExists(path, ref)\n }\n\n async applyPlan(input: LocalApplyPlanInput): Promise<LocalApplyResult> {\n const tx = await createTransaction(this.projectRoot, input.branch, {\n workflowOverride: input.workflowOverride,\n })\n try {\n await tx.write(async (wt) => {\n await applyChangesToWorktree(wt, input.changes)\n })\n await tx.commit(input.message, input.context)\n const gitResult = await tx.complete()\n return {\n sha: gitResult.commit,\n message: input.message,\n author: {\n name: process.env['CONTENTRAIN_AUTHOR_NAME'] ?? DEFAULT_AUTHOR_NAME,\n email: process.env['CONTENTRAIN_AUTHOR_EMAIL'] ?? DEFAULT_AUTHOR_EMAIL,\n },\n timestamp: new Date().toISOString(),\n workflowAction: gitResult.action,\n sync: gitResult.sync,\n warning: gitResult.warning,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n listBranches(prefix?: string): Promise<Branch[]> {\n return listBranchesOp(this.projectRoot, prefix)\n }\n\n async createBranch(name: string, fromRef?: string): Promise<void> {\n const resolved = fromRef ?? CONTENTRAIN_BRANCH\n await createBranchOp(this.projectRoot, name, resolved)\n }\n\n deleteBranch(name: string): Promise<void> {\n return deleteBranchOp(this.projectRoot, name)\n }\n\n getBranchDiff(branch: string, base?: string): Promise<FileDiff[]> {\n const resolved = base ?? CONTENTRAIN_BRANCH\n return getBranchDiffOp(this.projectRoot, branch, resolved)\n }\n\n mergeBranch(branch: string, into: string): Promise<MergeResult> {\n return mergeBranchOp(this.projectRoot, branch, into)\n }\n\n isMerged(branch: string, into?: string): Promise<boolean> {\n const resolved = into ?? CONTENTRAIN_BRANCH\n return isMergedOp(this.projectRoot, branch, resolved)\n }\n\n getDefaultBranch(): Promise<string> {\n return getDefaultBranchOp(this.projectRoot)\n }\n}\n"],"mappings":"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"}