gitifact 0.4.3 → 0.4.4

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 (29) hide show
  1. package/README.md +4 -2
  2. package/dist/browser/assets/{Markdown-GTI3nRCA.js → Markdown-Drk5rJF_.js} +1 -1
  3. package/dist/browser/assets/{MetadataListItem-c6t7SM9J.js → MetadataListItem-o7NlLubs.js} +1 -1
  4. package/dist/browser/assets/{Table-BJ_vfcZE.js → Table-DmxmodQG.js} +1 -1
  5. package/dist/browser/assets/{about-5hmfP7Oq.js → about-BFpb4QWu.js} +1 -1
  6. package/dist/browser/assets/{changelog-jpn_8GXM.js → changelog-rtqqVzIW.js} +1 -1
  7. package/dist/browser/assets/{contributors._email-DUoflTLI.js → contributors._email-CsIeETiA.js} +1 -1
  8. package/dist/browser/assets/{contributors.index-DfuU03rG.js → contributors.index-BVn-RzjO.js} +1 -1
  9. package/dist/browser/assets/{document-DeNuHzWM.js → document-BGXuvcs6.js} +1 -1
  10. package/dist/browser/assets/{features._featureId-mcSjZyhs.js → features._featureId-Bw_4HsoD.js} +1 -1
  11. package/dist/browser/assets/{requirements-QHPB6OmC.js → features.index-Cf-ct7tk.js} +1 -1
  12. package/dist/browser/assets/{git-Cj31Qjk8.js → git-E_25yRrM.js} +1 -1
  13. package/dist/browser/assets/{guides._documentId-BuOa1m0p.js → guides._documentId-CJIzrRob.js} +1 -1
  14. package/dist/browser/assets/{guides.index-BNK_skIs.js → guides.index-Bh0g2XhD.js} +1 -1
  15. package/dist/browser/assets/{index-CdO3EM0n.js → index-Bmk5GeZg.js} +3 -3
  16. package/dist/browser/assets/{page-header-CBWpQAOT.js → page-header-Cmn2UulE.js} +2 -2
  17. package/dist/browser/assets/{product-DZ2_YsMy.js → product-lcmaeBnU.js} +1 -1
  18. package/dist/browser/assets/{product.document-C5VnB7EK.js → product.document-BH-jLWyC.js} +1 -1
  19. package/dist/browser/assets/{product.index-DybzHICn.js → product.index-3Hnsh67g.js} +1 -1
  20. package/dist/browser/assets/{request-state-CtVGSFDZ.js → request-state-hT3U3nPp.js} +1 -1
  21. package/dist/browser/assets/{features.index-PaoXpYJM.js → requirements-C5TAa2tx.js} +1 -1
  22. package/dist/browser/assets/{routes-BWWTOV27.js → routes-D_2lOqSD.js} +1 -1
  23. package/dist/browser/assets/{settings-DN1DDBYj.js → settings-Dzak94qo.js} +1 -1
  24. package/dist/browser/index.html +8 -8
  25. package/dist/i18n/ko/block.md +23 -11
  26. package/dist/i18n/ko/changelog.md +9 -0
  27. package/dist/i18n/ko/docs/workflow.md +2 -2
  28. package/dist/main.js +116 -18
  29. package/package.json +3 -3
@@ -1,11 +1,19 @@
1
+ ## Gitifact Guide
2
+
1
3
  gitifact v{version} · {language} · 저장 규약 schemaVersion 1
4
+
2
5
  CLI: 모든 명령은 `gitifact <cmd>`로 실행한다. 프로젝트 지침이 다른 실행 방법을 지정하면 그것을 따른다.
3
6
 
4
- 시작할 때:
7
+ ### 시작할 때
8
+
9
+ - `gitifact` 명령이 없으면 이 프로젝트에 참여하는 데 필요한 CLI가 설치되지 않은 것이다. 사용자에게 알리고 동의를 받아 `npm install -g gitifact@{version}`으로 설치한 뒤 진행한다. 설치하지 못하면 명세 저장·커밋을 추측으로 대신하지 않는다.
5
10
  - `gitifact spec working`으로 제품 설명·지침·기능 명세를 읽고 git status와 기존 staging을 확인한다.
6
11
  - 이 블록은 요약이다. 상세 형식은 `gitifact docs <topic>`으로 읽고 기억으로 채우지 않는다.
7
12
 
8
- 무엇을 요구사항으로 남기는가 (제품 동작과 유지할 제약만):
13
+ ### 무엇을 요구사항으로 남기는가
14
+
15
+ 제품 동작과 유지할 제약만 남긴다.
16
+
9
17
  | 요청 | 처리 |
10
18
  | --- | --- |
11
19
  | 게시물을 삭제할 수 있게 해주세요 | 요구사항으로 정리한다 |
@@ -13,7 +21,8 @@ CLI: 모든 명령은 `gitifact <cmd>`로 실행한다. 프로젝트 지침이
13
21
  | 지금 푸시해주세요 | 작업 지시다. 등록하지 않는다 |
14
22
  | 외부 서비스 없이 동작해야 합니다 | 제품 제약으로 명세에 반영한다 |
15
23
 
16
- 규칙:
24
+ ### 규칙
25
+
17
26
  - 명세를 저장하기 전에 `gitifact docs spec`을 읽는다. ID는 CLI가 발급한 값만 쓴다.
18
27
  - 새 기능은 requirements.md와 design.md를 함께 정리한다(`gitifact docs design`). 요구사항만 요청받으면 따른다.
19
28
  - 커밋 요청을 받으면 `gitifact docs commit`을 읽고 명세·이유·코드·테스트를 함께 커밋한다.
@@ -23,11 +32,14 @@ CLI: 모든 명령은 `gitifact <cmd>`로 실행한다. 프로젝트 지침이
23
32
  - save·commit 입력 JSON은 `spec working`이 알려 준 inputs 경로에 쓴다. 성공하면 CLI가 지운다. 조회 결과와 docs 출력은 파일로 저장하지 않고 필요할 때 다시 실행한다.
24
33
  - 사용자가 요구사항·프로젝트 현황·변경 이력을 보여 달라고 하면 `gitifact browser`를 백그라운드로 실행하고 출력된 URL을 알려 준다. 채팅 요약으로 대신하지 않는다.
25
34
 
26
- MORE CLI:
27
- docs <topic> {topics}
28
- spec working 현재 명세·제품·지침 전체, stamp, 입력 파일 경로 (--stamp, --feature <이름>, --ids)
29
- spec save --file <json|->: 요구사항·설계·문서 저장
30
- spec commit --file <json|->: 변경 이유 기록과 커밋을 한 번에
31
- browser 읽기 전용 브라우저 서버 실행, URL 출력 후 계속 실행
32
- update 새 버전 확인과 설치 안내, 이 블록을 현재 버전으로 갱신
33
- init 처음 도입할 때 설정과 이 블록을 만든다
35
+ ### 명령
36
+
37
+ - `docs <topic>`: {topics}
38
+ - `spec working`: 현재 명세·제품·지침 전체, stamp, 입력 파일 경로 (`--stamp`, `--feature <이름>`, `--ids`)
39
+ - `spec save --file <json|->`: 요구사항·설계·문서 저장
40
+ - `spec commit --file <json|->`: 변경 이유 기록과 커밋을 한 번에
41
+ - `browser`: 읽기 전용 브라우저 서버 실행, URL 출력 후 계속 실행
42
+ - `update [--commit]`: 새 버전 확인과 설치 안내, 이 블록을 현재 버전으로 갱신. `--commit`은 블록만 바뀐 파일을 고정 메시지로 커밋한다
43
+ - `init`: 처음 도입할 때 설정과 이 블록을 만든다
44
+
45
+ ---
@@ -1,3 +1,12 @@
1
+ ## 0.4.4 - 2026-09-17
2
+ ### Added
3
+ - `update --commit`이 블록 안만 바뀐 지침 파일을 `chore(gitifact): refresh GITIFACT block to v<버전>` 메시지로 그 파일만 커밋합니다. 다른 staging은 그대로 남고 훅·서명도 평소대로 실행됩니다. 블록 밖에도 수정이 있거나, 추적하지 않는 파일이거나, Git이 커밋을 거부하면 커밋하지 않고 이유를 알립니다.
4
+ - GITIFACT 블록이 `gitifact` 명령이 없는 환경을 안내합니다. 에이전트는 사용자에게 알리고 동의를 받아 블록에 적힌 버전으로 설치합니다.
5
+ ### Changed
6
+ - GITIFACT 블록을 `## Gitifact Guide` 제목, 절 제목, 목록으로 정리하고 끝에 구분선을 넣었습니다. Markdown으로 볼 때 명령 목록이 한 줄로 합쳐지지 않습니다. `update` 또는 `init`을 다시 실행하면 갱신됩니다.
7
+ - 브라우저의 업데이트 안내 문장이 설치 후 `gitifact update --commit`을 실행하도록 안내합니다.
8
+ - `update` 출력 계약이 version 2가 되었습니다. 커밋 결과를 담는 `commit` 필드가 추가됐습니다.
9
+
1
10
  ## 0.4.3 - 2026-09-17
2
11
  ### Fixed
3
12
  - 브라우저의 새 버전 안내 창에서 제목과 닫기 버튼이 창 가장자리에 붙어 잘려 보이던 문제를 고쳤습니다.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 시작과 형식 확인
6
6
 
7
- 현재 경로·브랜치·Git 상태와 기존 staging을 확인하고 적용되는 AGENTS.md·CLAUDE.md를 원문으로 읽는다. 지침에서 지정한 CLI를 사용한다. 아래 `gitifact`는 그 실행 방법을 뜻한다. CLI가 없다면 설치·전역 설정 변경을 임의로 하지 않고 가능한 조사부터 진행한다.
7
+ 현재 경로·브랜치·Git 상태와 기존 staging을 확인하고 적용되는 AGENTS.md·CLAUDE.md를 원문으로 읽는다. 지침에서 지정한 CLI를 사용한다. 아래 `gitifact`는 그 실행 방법을 뜻한다. CLI가 없다면 이 프로젝트에 참여하는 데 필요한 도구가 빠진 것이다. 설치·전역 설정 변경을 임의로 하지 않고, 사용자에게 알린 뒤 동의를 받아 블록 첫머리에 적힌 버전으로 설치한다(`npm install -g gitifact@<버전>`). 설치 전에는 가능한 조사부터 진행하고 명세 저장·커밋을 추측으로 대신하지 않는다.
8
8
 
9
9
  설정과 실제 파일, CLI 도움말을 함께 확인해 다음 중 하나의 흐름을 선택한다. 명령이 존재한다는 사실만으로 프로젝트 사용이나 전환이 허용되지는 않는다.
10
10
 
@@ -12,7 +12,7 @@
12
12
  - **기존 형식:** workflow-1·prototype-1·init-1 설정은 현재 CLI가 조회·기록하지 않는다. 기존 기록을 삭제하거나 새 형식으로 가장하지 않고, 기존 기록을 읽으려면 0.4.0 이하 CLI가 필요하다고 알린다.
13
13
  - **미도입:** 도입이 허용됐으면 Git 상태와 지침을 확인하고 `init --dry-run`, `init`으로 연결한다. Git 저장소가 없으면 Git 생성 권한을 확인한다. 기존 변경과 staging을 보존한다.
14
14
 
15
- init은 `.gitifact/config.json`과 도입 기준선을 만들고, AGENTS.md 등 에이전트 지침 파일에 `<!-- GITIFACT:START -->`와 `<!-- GITIFACT:END -->` 사이의 블록을 쓴다. 마커 바깥의 내용은 건드리지 않는다. 요구사항·커밋은 만들지 않는다. 블록은 규칙의 요약이며, 상세 형식은 `gitifact docs <topic>`으로 읽는다. CLI를 업데이트한 뒤 `update`(또는 `init`)를 실행하면 블록이 갱신된다. `update`는 새 버전 여부와 설치 방법도 알려 주며 설치를 직접 실행하지는 않는다.
15
+ init은 `.gitifact/config.json`과 도입 기준선을 만들고, AGENTS.md 등 에이전트 지침 파일에 `<!-- GITIFACT:START -->`와 `<!-- GITIFACT:END -->` 사이의 블록을 쓴다. 마커 바깥의 내용은 건드리지 않는다. 요구사항·커밋은 만들지 않는다. 블록은 규칙의 요약이며, 상세 형식은 `gitifact docs <topic>`으로 읽는다. CLI를 업데이트한 뒤 `update`(또는 `init`)를 실행하면 블록이 갱신된다. `update`는 새 버전 여부와 설치 방법도 알려 주며 설치를 직접 실행하지는 않는다. 사용자가 업데이트를 요청하면 `update --commit`을 쓴다. 블록 안만 바뀐 지침 파일을 `chore(gitifact): refresh GITIFACT block to v<버전>` 메시지로 그 파일만 커밋하고, 다른 staging은 그대로 둔다. 블록 밖에도 수정이 있거나 추적하지 않는 파일이거나 Git이 커밋을 거부하면 커밋하지 않고 `commit.reason`으로 알린다. 이때 에이전트가 메시지를 바꿔 대신 커밋하지 않고 사용자에게 알린다.
16
16
 
17
17
  구형 기록의 자동 마이그레이션은 아직 없다. 명시적인 전환 작업은 필요한 요구사항을 검증한 뒤 합의한 보존·제거 범위로 처리한다. init을 재실행하거나 설정을 임의 변경해 전환을 우회하지 않는다.
18
18
 
package/dist/main.js CHANGED
@@ -24563,11 +24563,20 @@ var browserSessionV2 = external_exports.strictObject({
24563
24563
  update: updateStateV1
24564
24564
  });
24565
24565
 
24566
- // ../../packages/contracts/dist/versions/update-v1.js
24567
- var updateV1 = external_exports.discriminatedUnion("ok", [
24566
+ // ../../packages/contracts/dist/versions/update-v2.js
24567
+ var commitReason = external_exports.enum(["untracked", "other-changes", "commit-failed"]);
24568
+ var agentDocsCommit = external_exports.strictObject({
24569
+ state: external_exports.enum(["not-requested", "nothing", "committed", "skipped"]),
24570
+ commit: external_exports.string().nullable(),
24571
+ paths: external_exports.array(external_exports.string()),
24572
+ message: external_exports.string().nullable(),
24573
+ reason: commitReason.nullable(),
24574
+ detail: external_exports.string().nullable()
24575
+ }).refine((value) => value.state === "committed" === (value.commit !== null) && value.state === "skipped" === (value.reason !== null), "commit is set exactly when committed, reason exactly when skipped.");
24576
+ var updateV2 = external_exports.discriminatedUnion("ok", [
24568
24577
  external_exports.strictObject({
24569
24578
  contract: external_exports.literal("update"),
24570
- version: external_exports.literal(1),
24579
+ version: external_exports.literal(2),
24571
24580
  ok: external_exports.literal(true),
24572
24581
  cliVersion: external_exports.string().min(1),
24573
24582
  update: updateStateV1,
@@ -24575,9 +24584,10 @@ var updateV1 = external_exports.discriminatedUnion("ok", [
24575
24584
  install: external_exports.strictObject({ npmGlobal: external_exports.string().min(1) }).nullable(),
24576
24585
  // refreshed: blocks whose version line differed were rewritten. current: every block already matched.
24577
24586
  // not-initialized: no .gitifact config here, so only the version check ran. no-block: no file carries a block.
24578
- agentDocs: external_exports.strictObject({ state: external_exports.enum(["refreshed", "current", "not-initialized", "no-block"]), paths: external_exports.array(external_exports.string()) })
24587
+ agentDocs: external_exports.strictObject({ state: external_exports.enum(["refreshed", "current", "not-initialized", "no-block"]), paths: external_exports.array(external_exports.string()) }),
24588
+ commit: agentDocsCommit
24579
24589
  }),
24580
- external_exports.strictObject({ contract: external_exports.literal("update"), version: external_exports.literal(1), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24590
+ external_exports.strictObject({ contract: external_exports.literal("update"), version: external_exports.literal(2), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24581
24591
  ]);
24582
24592
 
24583
24593
  // src/shared/i18n/ko/messages.json
@@ -24814,6 +24824,7 @@ var messages_default2 = {
24814
24824
  "docs.summary.product": "PRODUCT.md\uC640 guides \uBB38\uC11C\uC758 \uC5ED\uD560\uACFC \uC800\uC7A5 \uBA85\uB839",
24815
24825
  "docs.summary.commit": "spec commit \uC785\uB825, \uC774\uC720 \uAE30\uB85D, \uC2E4\uD328 \uD6C4 \uBCF5\uAD6C",
24816
24826
  "help.browserNoUpdateCheck": "\uC0C8 \uBC84\uC804 \uD655\uC778\uC744 \uC704\uD55C npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC870\uD68C\uB97C \uB054 (GITIFACT_NO_UPDATE_CHECK\uC640 \uAC19\uC74C)",
24827
+ "help.updateCommit": "\uAC31\uC2E0\uD55C GITIFACT \uBE14\uB85D\uC774 HEAD\uC640 \uBE14\uB85D \uC548\uC5D0\uC11C\uB9CC \uB2E4\uB974\uBA74 \uADF8 \uD30C\uC77C\uB9CC \uACE0\uC815 \uBA54\uC2DC\uC9C0\uB85C \uCEE4\uBC0B",
24817
24828
  "help.update": "\uC0C8 \uBC84\uC804 \uD655\uC778\uACFC \uC124\uCE58 \uBC29\uBC95 \uC548\uB0B4, \uD504\uB85C\uC81D\uD2B8 \uC9C0\uCE68 \uD30C\uC77C\uC758 GITIFACT \uBE14\uB85D\uC744 \uD604\uC7AC \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0",
24818
24829
  "server.badLanguage": "\uC5B8\uC5B4 \uCF54\uB4DC\uB97C \uD655\uC778\uD558\uC138\uC694.",
24819
24830
  "server.changelogNotFound": "\uD328\uCE58\uB178\uD2B8\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
@@ -24824,11 +24835,17 @@ var messages_default2 = {
24824
24835
  "update.text.disabled": "\uC0C8 \uBC84\uC804 \uD655\uC778\uC774 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4.",
24825
24836
  "update.text.unavailable": "\uC0C8 \uBC84\uC804\uC744 \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uC138\uC694.",
24826
24837
  "update.text.install": "npm \uC804\uC5ED \uC124\uCE58\uC6A9: {command}",
24827
- "update.text.afterInstall": "\uC124\uCE58\uD55C \uB4A4 gitifact update\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uC9C0\uCE68 \uBE14\uB85D\uC774 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB429\uB2C8\uB2E4.",
24838
+ "update.text.afterInstall": "\uC124\uCE58\uD55C \uB4A4 gitifact update --commit\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uC9C0\uCE68 \uBE14\uB85D\uC774 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB418\uACE0 \uCEE4\uBC0B\uB429\uB2C8\uB2E4.",
24828
24839
  "update.text.blockRefreshed": "\uC9C0\uCE68 \uBE14\uB85D \uAC31\uC2E0: {paths}",
24829
24840
  "update.text.blockCurrent": "\uC9C0\uCE68 \uBE14\uB85D\uC740 \uC774\uBBF8 \uD604\uC7AC \uBC84\uC804\uC785\uB2C8\uB2E4: {paths}",
24830
24841
  "update.text.noBlock": "GITIFACT \uBE14\uB85D\uC774 \uC788\uB294 \uC9C0\uCE68 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. gitifact init\uC73C\uB85C \uC124\uCE58\uD558\uC138\uC694.",
24831
24842
  "update.text.notInitialized": "\uCD08\uAE30\uD654\uB41C \uD504\uB85C\uC81D\uD2B8\uAC00 \uC544\uB2C8\uC5B4\uC11C \uBC84\uC804\uB9CC \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4.",
24843
+ "update.text.committed": "\uC9C0\uCE68 \uBE14\uB85D \uCEE4\uBC0B: {commit} ({paths})",
24844
+ "update.text.commitNothing": "\uCEE4\uBC0B\uD560 \uC9C0\uCE68 \uBE14\uB85D \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
24845
+ "update.text.commitSkipped.untracked": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. Git\uC774 \uCD94\uC801\uD558\uC9C0 \uC54A\uB294 \uC9C0\uCE68 \uD30C\uC77C\uC785\uB2C8\uB2E4: {paths}",
24846
+ "update.text.commitSkipped.other-changes": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uBE14\uB85D \uBC16\uC5D0\uB3C4 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uC218\uC815\uC774 \uC788\uC2B5\uB2C8\uB2E4: {paths}",
24847
+ "update.text.commitSkipped.commit-failed": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. git commit\uC774 \uAC70\uBD80\uB410\uACE0 \uAC31\uC2E0\uD55C \uD30C\uC77C\uC740 \uADF8\uB300\uB85C \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4: {paths}",
24848
+ "update.commitResultUnknown": "\uCEE4\uBC0B \uACB0\uACFC\uB97C \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. HEAD {commit}\uC758 \uBD80\uBAA8\uC640 \uBCC0\uACBD \uD30C\uC77C\uC744 \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.",
24832
24849
  "update.failed": "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4."
24833
24850
  };
24834
24851
 
@@ -26204,7 +26221,39 @@ ${t2("init.text.agentDocs")}: ${docs}
26204
26221
  }
26205
26222
  }
26206
26223
 
26224
+ // src/adapters/git/agent-docs-commit.ts
26225
+ function agentDocsGit(root, inherited = process.env) {
26226
+ const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
26227
+ const runner = createGitRunner();
26228
+ const git = (args, acceptedExitCodes = [0]) => runner(
26229
+ ["-c", "core.fsmonitor=false", ...args],
26230
+ { cwd: root, env, timeoutMs: 12e4, maxBytes: 16 * 1024 * 1024, acceptedExitCodes }
26231
+ );
26232
+ return {
26233
+ async head() {
26234
+ return (await git(["rev-parse", "-q", "--verify", "HEAD^{commit}"], [0, 1])).toString("utf8").trim() || null;
26235
+ },
26236
+ // The committed text of a path, or null when HEAD does not track it.
26237
+ async committed(head, path3) {
26238
+ if ((await git(["ls-tree", "-z", head, "--", path3])).length === 0) return null;
26239
+ return (await git(["cat-file", "blob", head + ":" + path3])).toString("utf8");
26240
+ },
26241
+ // `--only` commits the working content of these paths and leaves every other staged entry staged. Hooks and signing stay on.
26242
+ async commitOnly(paths2, message) {
26243
+ await git(["commit", "--only", "-m", message, "--", ...paths2]);
26244
+ },
26245
+ async changedPaths(commit) {
26246
+ return (await git(["diff-tree", "--no-commit-id", "--name-only", "--no-renames", "-r", "-z", "--root", commit])).toString("utf8").split("\0").filter(Boolean).sort();
26247
+ },
26248
+ async parents(commit) {
26249
+ return (await git(["rev-list", "--parents", "-n", "1", commit])).toString("utf8").trim().split(" ").slice(1);
26250
+ }
26251
+ };
26252
+ }
26253
+
26207
26254
  // src/commands/update.ts
26255
+ var blockCommitMessage = (version2) => "chore(gitifact): refresh GITIFACT block to v" + version2;
26256
+ var commitState = (state, extra = {}) => ({ state, commit: null, paths: [], message: null, reason: null, detail: null, ...extra });
26208
26257
  async function refreshBlocks(cwd, version2, env, controls) {
26209
26258
  const repo = initRepository(cwd, env);
26210
26259
  let first;
@@ -26219,31 +26268,72 @@ async function refreshBlocks(cwd, version2, env, controls) {
26219
26268
  if (config2 === void 0) return { state: "not-initialized", paths: [] };
26220
26269
  const root = first.state.repository.rootPath;
26221
26270
  const plan = await planAgentDocs(root, { version: version2, onlyExisting: true, ...controls });
26222
- if (plan.paths.length === 0) return { state: "no-block", paths: [] };
26223
- if (plan.writes.length === 0) return { state: "current", paths: plan.paths };
26271
+ if (plan.paths.length === 0) return { state: "no-block", paths: [], root };
26272
+ if (plan.writes.length === 0) return { state: "current", paths: plan.paths, root, candidates: plan.paths };
26224
26273
  const unchanged = async () => {
26225
26274
  if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== config2) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
26226
26275
  };
26227
- return { state: "refreshed", paths: await applyAgentDocs(root, plan, unchanged) };
26276
+ const applied = await applyAgentDocs(root, plan, unchanged);
26277
+ return { state: "refreshed", paths: applied, root, candidates: plan.paths };
26278
+ }
26279
+ function outsideBlock(text2) {
26280
+ const normalized2 = text2.replace(/\r\n/g, "\n");
26281
+ try {
26282
+ const found = findBlock(normalized2);
26283
+ return found ? normalized2.slice(0, found.start) + AGENT_START + normalized2.slice(found.end) : null;
26284
+ } catch {
26285
+ return null;
26286
+ }
26287
+ }
26288
+ async function commitBlocks(docs, version2, env) {
26289
+ if (!docs.root || !docs.candidates?.length) return commitState("nothing");
26290
+ const git = agentDocsGit(docs.root, env);
26291
+ const head = await git.head();
26292
+ const changed = [];
26293
+ for (const path3 of docs.candidates) {
26294
+ const working = await managedRead(docs.root, path3);
26295
+ const committed = head ? await git.committed(head, path3) : null;
26296
+ if (working === null) continue;
26297
+ if (committed === null) return commitState("skipped", { paths: [path3], reason: "untracked" });
26298
+ if (committed.replace(/\r\n/g, "\n") === working.replace(/\r\n/g, "\n")) continue;
26299
+ const before = outsideBlock(committed);
26300
+ if (before === null || before !== outsideBlock(working)) return commitState("skipped", { paths: [path3], reason: "other-changes" });
26301
+ changed.push(path3);
26302
+ }
26303
+ if (changed.length === 0) return commitState("nothing");
26304
+ const message = blockCommitMessage(version2);
26305
+ let failure2;
26306
+ try {
26307
+ await git.commitOnly(changed, message);
26308
+ } catch (error62) {
26309
+ failure2 = error62;
26310
+ }
26311
+ const after = await git.head();
26312
+ if (!after || after === head) return commitState("skipped", { paths: changed, reason: "commit-failed", detail: failure2 instanceof Error ? failure2.message : null });
26313
+ const parents = await git.parents(after);
26314
+ if (parents.length !== 1 || parents[0] !== head) throw new InitError("COMMIT_RESULT_UNKNOWN", t2("update.commitResultUnknown", { commit: after }));
26315
+ return commitState("committed", { commit: after, paths: await git.changedPaths(after), message });
26228
26316
  }
26229
26317
  async function updateCommand(cwd, version2, env = process.env, controls = {}) {
26230
26318
  const [update, agentDocs] = await Promise.all([
26231
26319
  updateCheckDisabled(env) ? disabledUpdate : resolveUpdate(version2, controls.fetchLatest ?? fetchLatestVersion, void 0, controls.timeoutMs),
26232
26320
  refreshBlocks(cwd, version2, env, controls)
26233
26321
  ]);
26234
- return updateV1.parse({
26322
+ const commit = controls.commit ? await commitBlocks(agentDocs, version2, env) : commitState("not-requested");
26323
+ return updateV2.parse({
26235
26324
  contract: "update",
26236
- version: 1,
26325
+ version: 2,
26237
26326
  ok: true,
26238
26327
  cliVersion: version2,
26239
26328
  update,
26240
26329
  install: update.status === "available" ? { npmGlobal: npmGlobalInstall(update.latestVersion) } : null,
26241
- agentDocs
26330
+ agentDocs: { state: agentDocs.state, paths: agentDocs.paths },
26331
+ commit
26242
26332
  });
26243
26333
  }
26244
26334
  async function runUpdate(options, version2) {
26245
26335
  try {
26246
- const dto = await updateCommand(process.cwd(), version2);
26336
+ const dto = await updateCommand(process.cwd(), version2, process.env, { commit: !!options.commit });
26247
26337
  if (!dto.ok) throw new Error("unreachable");
26248
26338
  if (options.format !== "text") {
26249
26339
  process.stdout.write(JSON.stringify(dto) + "\n");
@@ -26253,11 +26343,19 @@ async function runUpdate(options, version2) {
26253
26343
  lines.push(dto.update.status === "available" ? t2("update.text.available", { version: dto.update.latestVersion }) : dto.update.status === "up-to-date" ? t2("update.text.upToDate") : dto.update.status === "disabled" ? t2("update.text.disabled") : t2("update.text.unavailable"));
26254
26344
  if (dto.install) lines.push(t2("update.text.install", { command: dto.install.npmGlobal }), t2("update.text.afterInstall"));
26255
26345
  lines.push(dto.agentDocs.state === "refreshed" ? t2("update.text.blockRefreshed", { paths: dto.agentDocs.paths.join(", ") }) : dto.agentDocs.state === "current" ? t2("update.text.blockCurrent", { paths: dto.agentDocs.paths.join(", ") }) : dto.agentDocs.state === "no-block" ? t2("update.text.noBlock") : t2("update.text.notInitialized"));
26346
+ const { commit } = dto;
26347
+ if (commit.state === "committed") lines.push(t2("update.text.committed", { commit: commit.commit.slice(0, 12), paths: commit.paths.join(", ") }));
26348
+ else if (commit.state === "nothing") lines.push(t2("update.text.commitNothing"));
26349
+ else if (commit.state === "skipped") {
26350
+ const paths2 = commit.paths.join(", ");
26351
+ const text2 = commit.reason === "untracked" ? t2("update.text.commitSkipped.untracked", { paths: paths2 }) : commit.reason === "other-changes" ? t2("update.text.commitSkipped.other-changes", { paths: paths2 }) : t2("update.text.commitSkipped.commit-failed", { paths: paths2 });
26352
+ lines.push(text2 + (commit.detail ? " (" + commit.detail + ")" : ""));
26353
+ }
26256
26354
  process.stdout.write(lines.join("\n") + "\n");
26257
26355
  } catch (error62) {
26258
26356
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
26259
26357
  const failure2 = { code: known ? error62.code : "UPDATE_FAILED", message: known ? error62.message : t2("update.failed") };
26260
- process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(updateV1.parse({ contract: "update", version: 1, ok: false, error: failure2 })) + "\n");
26358
+ process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(updateV2.parse({ contract: "update", version: 2, ok: false, error: failure2 })) + "\n");
26261
26359
  process.exitCode = 1;
26262
26360
  }
26263
26361
  }
@@ -27039,16 +27137,16 @@ async function runMigrate(options) {
27039
27137
  }
27040
27138
 
27041
27139
  // src/main.ts
27042
- var program2 = new Command().name("gitifact").description(t2("help.program")).version("0.4.3").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
27140
+ var program2 = new Command().name("gitifact").description(t2("help.program")).version("0.4.4").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
27043
27141
  program2.command("status").description(t2("help.status")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action(async (options) => {
27044
27142
  await runStatus(options.format);
27045
27143
  });
27046
- program2.command("browser").description(t2("help.browser")).allowExcessArguments(false).option("--port <port>", t2("help.browserPort"), parsePort, 0).option("--dev", t2("help.browserDev")).option("--no-update-check", t2("help.browserNoUpdateCheck")).action((options) => runBrowser(options, "0.4.3"));
27047
- program2.command("init").description(t2("help.init")).allowExcessArguments(false).option("--dry-run", t2("help.initDryRun")).addOption(new Option("--agent <agent>", t2("help.initAgent")).choices([...agentPresetNames])).addOption(new Option("--remove-agents", t2("help.initRemoveAgents")).conflicts("skipAgents")).option("--skip-agents", t2("help.initSkipAgents")).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action((options) => runInit(options, "0.4.3"));
27144
+ program2.command("browser").description(t2("help.browser")).allowExcessArguments(false).option("--port <port>", t2("help.browserPort"), parsePort, 0).option("--dev", t2("help.browserDev")).option("--no-update-check", t2("help.browserNoUpdateCheck")).action((options) => runBrowser(options, "0.4.4"));
27145
+ program2.command("init").description(t2("help.init")).allowExcessArguments(false).option("--dry-run", t2("help.initDryRun")).addOption(new Option("--agent <agent>", t2("help.initAgent")).choices([...agentPresetNames])).addOption(new Option("--remove-agents", t2("help.initRemoveAgents")).conflicts("skipAgents")).option("--skip-agents", t2("help.initSkipAgents")).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action((options) => runInit(options, "0.4.4"));
27048
27146
  program2.command("docs").description(t2("help.docs")).argument("[topic]", t2("help.docsTopic")).allowExcessArguments(false).action(async (topic) => {
27049
27147
  await runDocs(topic);
27050
27148
  });
27051
- program2.command("update").description(t2("help.update")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action((options) => runUpdate(options, "0.4.3"));
27149
+ program2.command("update").description(t2("help.update")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).option("--commit", t2("help.updateCommit")).action((options) => runUpdate(options, "0.4.4"));
27052
27150
  program2.command("migrate").description(t2("help.migrate")).allowExcessArguments(false).option("--dry-run", t2("help.migrateDryRun")).action(runMigrate);
27053
27151
  var spec = program2.command("spec").description(t2("help.spec"));
27054
27152
  var replaced = t2("help.deprecated");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitifact",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Git-backed requirements and change history for coding agents.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "devDependencies": {
37
37
  "commander": "15.0.0",
38
38
  "esbuild": "0.28.2",
39
- "@gitifact/contracts": "0.0.0",
40
- "@gitifact/core": "0.0.0"
39
+ "@gitifact/core": "0.0.0",
40
+ "@gitifact/contracts": "0.0.0"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "node scripts/build.mjs",