gitifact 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/browser/assets/{about-NfIJSqpC.js → about-Dbyd48t6.js} +1 -1
- package/dist/browser/assets/{contributors._email-B6w1SA-1.js → contributors._email-BMqQlPdF.js} +1 -1
- package/dist/browser/assets/{contributors.index-Dkv636DW.js → contributors.index-Dk42HR-l.js} +1 -1
- package/dist/browser/assets/{features._featureId-B4Ba6Tbc.js → features._featureId-zHnvA0Go.js} +1 -1
- package/dist/browser/assets/{requirements-Db8psmjT.js → features.index-xgyvTm5d.js} +1 -1
- package/dist/browser/assets/git-D8SQ9LAI.js +1 -0
- package/dist/browser/assets/guides._documentId-jvtVZl4b.js +1 -0
- package/dist/browser/assets/guides.index-DKNn9kCJ.js +1 -0
- package/dist/browser/assets/{index-DMRG08Xu.js → index-Cul6rpdA.js} +4 -4
- package/dist/browser/assets/{page-header-it9YqWBS.js → page-header-pnZFm0-7.js} +1 -1
- package/dist/browser/assets/product-B_C25r7E.js +75 -0
- package/dist/browser/assets/product-DIl1IXZo.css +1 -0
- package/dist/browser/assets/product.index-IBLtgiMT.js +1 -0
- package/dist/browser/assets/{request-state-s3JHTg5D.js → request-state-q7VzEh1O.js} +2 -2
- package/dist/browser/assets/requirements-BmW8BEdu.js +1 -0
- package/dist/browser/assets/{routes-HW2hx-iw.js → routes-BDsb61_V.js} +1 -1
- package/dist/browser/index.html +5 -5
- package/dist/docs/commit.md +36 -0
- package/dist/docs/design.md +42 -0
- package/dist/docs/product.md +21 -0
- package/dist/docs/spec.md +54 -0
- package/dist/docs/workflow.md +42 -0
- package/dist/main.js +875 -466
- package/package.json +1 -1
- package/dist/browser/assets/features.index-Cfxr24mB.js +0 -1
- package/dist/browser/assets/git-D5knlkQy.js +0 -1
- package/dist/browser/assets/product-Cn69vcBM.css +0 -1
- package/dist/browser/assets/product-DAbLI-2U.js +0 -75
- package/dist/skills/gitifact-workflow/SKILL.md +0 -157
package/dist/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
-
var __export = (
|
|
3
|
+
var __export = (target, all) => {
|
|
4
4
|
for (var name in all)
|
|
5
|
-
__defProp(
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// scripts/node-require.mjs
|
|
@@ -1635,9 +1635,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1635
1635
|
* @param {string} invalidArgumentMessage
|
|
1636
1636
|
* @private
|
|
1637
1637
|
*/
|
|
1638
|
-
_callParseArg(
|
|
1638
|
+
_callParseArg(target, value, previous, invalidArgumentMessage) {
|
|
1639
1639
|
try {
|
|
1640
|
-
return
|
|
1640
|
+
return target.parseArg(value, previous);
|
|
1641
1641
|
} catch (err) {
|
|
1642
1642
|
if (err.code === "commander.invalidArgument") {
|
|
1643
1643
|
const message = `${invalidArgumentMessage} ${err.message}`;
|
|
@@ -1926,13 +1926,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1926
1926
|
* @param {string} source - expected values are default/config/env/cli/implied
|
|
1927
1927
|
* @return {Command} `this` command for chaining
|
|
1928
1928
|
*/
|
|
1929
|
-
setOptionValueWithSource(key, value,
|
|
1929
|
+
setOptionValueWithSource(key, value, source) {
|
|
1930
1930
|
if (this._storeOptionsAsProperties) {
|
|
1931
1931
|
this[key] = value;
|
|
1932
1932
|
} else {
|
|
1933
1933
|
this._optionValues[key] = value;
|
|
1934
1934
|
}
|
|
1935
|
-
this._optionValueSources[key] =
|
|
1935
|
+
this._optionValueSources[key] = source;
|
|
1936
1936
|
return this;
|
|
1937
1937
|
}
|
|
1938
1938
|
/**
|
|
@@ -1953,13 +1953,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1953
1953
|
* @return {string}
|
|
1954
1954
|
*/
|
|
1955
1955
|
getOptionValueSourceWithGlobals(key) {
|
|
1956
|
-
let
|
|
1956
|
+
let source;
|
|
1957
1957
|
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1958
1958
|
if (cmd.getOptionValueSource(key) !== void 0) {
|
|
1959
|
-
|
|
1959
|
+
source = cmd.getOptionValueSource(key);
|
|
1960
1960
|
}
|
|
1961
1961
|
});
|
|
1962
|
-
return
|
|
1962
|
+
return source;
|
|
1963
1963
|
}
|
|
1964
1964
|
/**
|
|
1965
1965
|
* Get user arguments from implied or explicit arguments.
|
|
@@ -2826,10 +2826,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2826
2826
|
const optionKey = option2.attributeName();
|
|
2827
2827
|
const optionValue = this.getOptionValue(optionKey);
|
|
2828
2828
|
const negativeOption = this.options.find(
|
|
2829
|
-
(
|
|
2829
|
+
(target) => target.negate && optionKey === target.attributeName()
|
|
2830
2830
|
);
|
|
2831
2831
|
const positiveOption = this.options.find(
|
|
2832
|
-
(
|
|
2832
|
+
(target) => !target.negate && optionKey === target.attributeName()
|
|
2833
2833
|
);
|
|
2834
2834
|
if (negativeOption && (negativeOption.presetArg === void 0 && optionValue === false || negativeOption.presetArg !== void 0 && optionValue === negativeOption.presetArg)) {
|
|
2835
2835
|
return negativeOption;
|
|
@@ -2839,8 +2839,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2839
2839
|
const getErrorMessage = (option2) => {
|
|
2840
2840
|
const bestOption = findBestOptionFromValue(option2);
|
|
2841
2841
|
const optionKey = bestOption.attributeName();
|
|
2842
|
-
const
|
|
2843
|
-
if (
|
|
2842
|
+
const source = this.getOptionValueSource(optionKey);
|
|
2843
|
+
if (source === "env") {
|
|
2844
2844
|
return `environment variable '${bestOption.envVar}'`;
|
|
2845
2845
|
}
|
|
2846
2846
|
return `option '${bestOption.flags}'`;
|
|
@@ -3492,12 +3492,36 @@ function parseManagedConfig(text2) {
|
|
|
3492
3492
|
var SpecPreviewError = class extends Error {
|
|
3493
3493
|
code = "INVALID_SPEC_PREVIEW";
|
|
3494
3494
|
};
|
|
3495
|
+
var DOCUMENT_KINDS = ["product", "guide"];
|
|
3496
|
+
var DOCUMENT_DIRS = { product: ".gitifact/product", guide: ".gitifact/guides" };
|
|
3497
|
+
var documentHistoryPath = (kind) => DOCUMENT_DIRS[kind] + "/history.jsonl";
|
|
3498
|
+
var PRODUCT_PATH = ".gitifact/product/PRODUCT.md";
|
|
3499
|
+
var PRODUCT_ASSET_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,120}\.(?:png|jpe?g|gif|svg|webp)$/i;
|
|
3500
|
+
function isProductAssetPath(path3) {
|
|
3501
|
+
const match = /^\.gitifact\/product\/([^/]+)$/.exec(path3);
|
|
3502
|
+
return !!match && PRODUCT_ASSET_NAME.test(match[1]);
|
|
3503
|
+
}
|
|
3495
3504
|
var token = "[a-z2-7]{10}";
|
|
3496
3505
|
var STORE_DIR = ".gitifact";
|
|
3497
3506
|
var LEGACY_STORE_DIR = ".tryce";
|
|
3498
3507
|
var STORE_DIRS = [STORE_DIR, LEGACY_STORE_DIR];
|
|
3499
3508
|
var marker = "(?:gitifact|tryce)";
|
|
3500
|
-
var recordPathPattern =
|
|
3509
|
+
var recordPathPattern = /^(?:\.(?:gitifact|tryce)\/spec\/[^/]+\/(?:requirements\.md|design\.md|history\.jsonl)|\.gitifact\/(?:product|guides)\/(?:(?:[^/]+\/)*[^/]+\.md|history\.jsonl))$/;
|
|
3510
|
+
var documentPathPattern = /^\.gitifact\/(product|guides)\/((?:[^/]+\/)*[^/]+\.md)$/;
|
|
3511
|
+
var documentHistoryPattern = /^\.gitifact\/(product|guides)\/history\.jsonl$/;
|
|
3512
|
+
var kindOfDir = (dir) => dir === "product" ? "product" : "guide";
|
|
3513
|
+
var nameToken = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3514
|
+
function validateDocumentRelativePath(relative) {
|
|
3515
|
+
const parts = relative.split("/");
|
|
3516
|
+
if (relative.length > 200 || parts.length > 8)
|
|
3517
|
+
fail("\uBB38\uC11C \uACBD\uB85C \uAE4A\uC774 \uB610\uB294 \uAE38\uC774 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4: " + relative);
|
|
3518
|
+
const file2 = parts.pop();
|
|
3519
|
+
if (!file2.endsWith(".md") || !nameToken.test(file2.slice(0, -3)))
|
|
3520
|
+
fail("\uBB38\uC11C \uD30C\uC77C \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uACFC .md\uC5EC\uC57C \uD569\uB2C8\uB2E4: " + relative);
|
|
3521
|
+
for (const part of parts)
|
|
3522
|
+
if (!nameToken.test(part) || part.length > 80)
|
|
3523
|
+
fail("\uBB38\uC11C \uD3F4\uB354 \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: " + relative);
|
|
3524
|
+
}
|
|
3501
3525
|
var storeRelative = (path3) => path3.replace(/^\.(?:gitifact|tryce)\//, "");
|
|
3502
3526
|
var reqId = new RegExp(`^R-${token}$`);
|
|
3503
3527
|
var fail = (message) => {
|
|
@@ -3512,10 +3536,10 @@ function renderDesignPreview(id, design) {
|
|
|
3512
3536
|
${design.body}
|
|
3513
3537
|
`;
|
|
3514
3538
|
}
|
|
3515
|
-
function parseDesignPreview(
|
|
3516
|
-
if (
|
|
3539
|
+
function parseDesignPreview(source, specId) {
|
|
3540
|
+
if (source.includes("\0") || /\r(?!\n)/.test(source))
|
|
3517
3541
|
fail("\uC798\uBABB\uB41C \uC124\uACC4 \uBB38\uC790\uC785\uB2C8\uB2E4.");
|
|
3518
|
-
const lines = normalized(
|
|
3542
|
+
const lines = normalized(source).split("\n");
|
|
3519
3543
|
if (!new RegExp(`^<!-- ${marker}-design: ${specId} -->$`).test(lines.shift() ?? ""))
|
|
3520
3544
|
fail("\uC124\uACC4\uC758 gitifact-design ID\uB294 \uAC19\uC740 \uAE30\uB2A5\uC758 S-ID\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
3521
3545
|
while (lines[0] === "")
|
|
@@ -3556,27 +3580,138 @@ function parseDesignPreview(source2, specId) {
|
|
|
3556
3580
|
fail("\uC124\uACC4 \uCF54\uB4DC \uBE14\uB85D\uC774 \uB2EB\uD788\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
|
|
3557
3581
|
return { title, body, requirements: [...references] };
|
|
3558
3582
|
}
|
|
3583
|
+
function renderDocument(doc) {
|
|
3584
|
+
return `<!-- gitifact-${doc.kind}: ${doc.id} -->
|
|
3585
|
+
|
|
3586
|
+
# ${doc.title}
|
|
3587
|
+
|
|
3588
|
+
${doc.body}
|
|
3589
|
+
`;
|
|
3590
|
+
}
|
|
3591
|
+
function parseDocument(path3, source) {
|
|
3592
|
+
const match = documentPathPattern.exec(path3);
|
|
3593
|
+
if (!match)
|
|
3594
|
+
fail("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBB38\uC11C \uACBD\uB85C: " + path3);
|
|
3595
|
+
const kind = kindOfDir(match[1]);
|
|
3596
|
+
if (kind === "product") {
|
|
3597
|
+
if (path3 !== PRODUCT_PATH)
|
|
3598
|
+
fail("\uC81C\uD488 \uD3F4\uB354\uC5D0\uB294 PRODUCT.md \uD558\uB098\uB9CC \uB461\uB2C8\uB2E4: " + path3);
|
|
3599
|
+
} else
|
|
3600
|
+
validateDocumentRelativePath(match[2]);
|
|
3601
|
+
if (source.includes("\0") || /\r(?!\n)/.test(source))
|
|
3602
|
+
fail("\uC798\uBABB\uB41C \uBB38\uC11C \uBB38\uC790: " + path3);
|
|
3603
|
+
const lines = normalized(source).split("\n");
|
|
3604
|
+
const id = new RegExp(`^<!-- gitifact-${kind}: ([PG]-${token}) -->$`).exec(lines.shift() ?? "")?.[1];
|
|
3605
|
+
if (!id || id[0] !== (kind === "product" ? "P" : "G"))
|
|
3606
|
+
return fail(`\uCCAB \uC904\uC5D0 \uC720\uD6A8\uD55C gitifact-${kind} \uC8FC\uC11D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: ` + path3);
|
|
3607
|
+
while (lines[0] === "")
|
|
3608
|
+
lines.shift();
|
|
3609
|
+
const heading = lines.shift();
|
|
3610
|
+
if (!heading?.startsWith("# ") || !heading.slice(2).trim())
|
|
3611
|
+
fail("\uBB38\uC11C \uC81C\uBAA9\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: " + path3);
|
|
3612
|
+
const title = heading.slice(2).trim();
|
|
3613
|
+
const body = normalized(lines.join("\n"));
|
|
3614
|
+
if (!body)
|
|
3615
|
+
fail("\uBB38\uC11C \uBCF8\uBB38\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: " + path3);
|
|
3616
|
+
let fence;
|
|
3617
|
+
for (const line of body.split("\n")) {
|
|
3618
|
+
if (fence) {
|
|
3619
|
+
if (new RegExp(`^ {0,3}${fence.char}{${fence.size},}\\s*$`).test(line))
|
|
3620
|
+
fence = void 0;
|
|
3621
|
+
continue;
|
|
3622
|
+
}
|
|
3623
|
+
const open6 = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
3624
|
+
if (open6) {
|
|
3625
|
+
fence = { char: open6[1][0], size: open6[1].length };
|
|
3626
|
+
continue;
|
|
3627
|
+
}
|
|
3628
|
+
if (/^#\s/.test(line))
|
|
3629
|
+
fail("\uBB38\uC11C \uCD5C\uC0C1\uC704 \uC81C\uBAA9\uC740 \uD558\uB098\uB9CC \uC501\uB2C8\uB2E4: " + path3);
|
|
3630
|
+
if (/<!-- (?:gitifact|tryce)-/.test(line))
|
|
3631
|
+
fail("\uBB38\uC11C \uBCF8\uBB38\uC5D0\uB294 gitifact \uC8FC\uC11D\uC744 \uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + path3);
|
|
3632
|
+
}
|
|
3633
|
+
if (fence)
|
|
3634
|
+
fail("\uBB38\uC11C \uCF54\uB4DC \uBE14\uB85D\uC774 \uB2EB\uD788\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: " + path3);
|
|
3635
|
+
return { id, kind, path: path3, title, body };
|
|
3636
|
+
}
|
|
3637
|
+
function parseDocumentFiles(files) {
|
|
3638
|
+
const sets = DOCUMENT_KINDS.map((kind) => ({ kind, documents: [], history: parsePreviewHistory(files.get(documentHistoryPath(kind)) ?? "", "documents") }));
|
|
3639
|
+
for (const [path3, source] of files) {
|
|
3640
|
+
if (!documentPathPattern.test(path3))
|
|
3641
|
+
continue;
|
|
3642
|
+
const doc = parseDocument(path3, source);
|
|
3643
|
+
sets.find((s) => s.kind === doc.kind).documents.push(doc);
|
|
3644
|
+
}
|
|
3645
|
+
for (const set2 of sets)
|
|
3646
|
+
set2.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
3647
|
+
validateDocumentSets(sets);
|
|
3648
|
+
return sets;
|
|
3649
|
+
}
|
|
3650
|
+
function validateDocumentSets(sets) {
|
|
3651
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3652
|
+
const paths2 = /* @__PURE__ */ new Set();
|
|
3653
|
+
const historyIds = /* @__PURE__ */ new Set();
|
|
3654
|
+
if (sets.map((s) => s.kind).sort().join(",") !== "guide,product")
|
|
3655
|
+
fail("\uBB38\uC11C \uC885\uB958 \uC9D1\uD569\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4.");
|
|
3656
|
+
for (const set2 of sets) {
|
|
3657
|
+
for (const d of set2.documents) {
|
|
3658
|
+
if (d.kind !== set2.kind)
|
|
3659
|
+
fail("\uBB38\uC11C \uC885\uB958\uAC00 \uD3F4\uB354\uC640 \uB2E4\uB985\uB2C8\uB2E4: " + d.path);
|
|
3660
|
+
if (ids.has(d.id))
|
|
3661
|
+
fail("\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBB38\uC11C ID: " + d.id);
|
|
3662
|
+
ids.add(d.id);
|
|
3663
|
+
const lower = d.path.toLowerCase();
|
|
3664
|
+
if (paths2.has(lower))
|
|
3665
|
+
fail("\uB300\uC18C\uBB38\uC790\uB9CC \uB2E4\uB978 \uBB38\uC11C \uACBD\uB85C: " + d.path);
|
|
3666
|
+
paths2.add(lower);
|
|
3667
|
+
}
|
|
3668
|
+
for (const h of set2.history) {
|
|
3669
|
+
if (historyIds.has(h.id))
|
|
3670
|
+
fail("\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBCF4\uC870 \uAE30\uB85D ID: " + h.id);
|
|
3671
|
+
historyIds.add(h.id);
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
var emptyDocumentSets = () => DOCUMENT_KINDS.map((kind) => ({ kind, documents: [], history: [] }));
|
|
3676
|
+
var asBundle = (value) => Array.isArray(value) ? { specs: value, documents: emptyDocumentSets() } : value;
|
|
3677
|
+
function parsePreviewBundle(files) {
|
|
3678
|
+
const bundle = { specs: parsePreviewFiles(files), documents: parseDocumentFiles(files) };
|
|
3679
|
+
validateBundle(bundle);
|
|
3680
|
+
return bundle;
|
|
3681
|
+
}
|
|
3682
|
+
function validateBundle(bundle) {
|
|
3683
|
+
validatePreviewSnapshot(bundle.specs);
|
|
3684
|
+
validateDocumentSets(bundle.documents);
|
|
3685
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3686
|
+
for (const h of [...bundle.specs.flatMap((s) => s.history), ...bundle.documents.flatMap((d) => d.history)]) {
|
|
3687
|
+
if (ids.has(h.id))
|
|
3688
|
+
fail("\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBCF4\uC870 \uAE30\uB85D ID: " + h.id);
|
|
3689
|
+
ids.add(h.id);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3559
3692
|
function designReferenceWarnings(specs) {
|
|
3560
3693
|
const known = new Set(specs.flatMap((s) => s.requirements.map((r) => r.id)));
|
|
3561
3694
|
return specs.flatMap((s) => (s.design?.requirements ?? []).filter((id) => !known.has(id)).map((id) => ({ specId: s.id, requirement: id, code: "MISSING_DESIGN_REFERENCE" })));
|
|
3562
3695
|
}
|
|
3563
3696
|
function parsePreviewFiles(files) {
|
|
3564
3697
|
const specs = [];
|
|
3565
|
-
for (const [path3,
|
|
3698
|
+
for (const [path3, source] of files) {
|
|
3699
|
+
if (documentPathPattern.test(path3) || documentHistoryPattern.test(path3))
|
|
3700
|
+
continue;
|
|
3566
3701
|
if (path3.endsWith("/requirements.md")) {
|
|
3567
|
-
specs.push(parseSpecPreview(path3,
|
|
3702
|
+
specs.push(parseSpecPreview(path3, source, files.get(path3.replace(/requirements\.md$/, "history.jsonl")) ?? "", files.get(path3.replace(/requirements\.md$/, "design.md"))));
|
|
3568
3703
|
} else if (!files.has(path3.replace(/(?:history\.jsonl|design\.md)$/, "requirements.md")))
|
|
3569
3704
|
fail("\uC694\uAD6C\uC0AC\uD56D \uBB38\uC11C \uC5C6\uB294 \uC124\uACC4 \uB610\uB294 \uC774\uC720 \uD30C\uC77C: " + path3);
|
|
3570
3705
|
}
|
|
3571
3706
|
validatePreviewSnapshot(specs);
|
|
3572
3707
|
return specs;
|
|
3573
3708
|
}
|
|
3574
|
-
function parseSpecPreview(path3,
|
|
3709
|
+
function parseSpecPreview(path3, source, history = "", designSource) {
|
|
3575
3710
|
if (!/^\.(?:gitifact|tryce)\/spec\/[^/]+\/requirements\.md$/.test(path3))
|
|
3576
3711
|
fail("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uACBD\uB85C: " + path3);
|
|
3577
|
-
if (
|
|
3712
|
+
if (source.includes("\0") || source.includes("\r") && /\r(?!\n)/.test(source))
|
|
3578
3713
|
fail("\uC798\uBABB\uB41C \uBA85\uC138 \uBB38\uC790: " + path3);
|
|
3579
|
-
const lines = normalized(
|
|
3714
|
+
const lines = normalized(source).split("\n");
|
|
3580
3715
|
const specMatch = new RegExp(`^<!-- ${marker}-spec: (S-${token}) -->$`).exec(lines[0] ?? "");
|
|
3581
3716
|
if (!specMatch)
|
|
3582
3717
|
fail("\uCCAB \uC904\uC5D0 \uC720\uD6A8\uD55C gitifact-spec \uC8FC\uC11D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: " + path3);
|
|
@@ -3651,10 +3786,11 @@ function parseSpecPreview(path3, source2, history = "", designSource) {
|
|
|
3651
3786
|
}
|
|
3652
3787
|
return { id: specMatch[1], path: path3, title, description: normalized(description), requirements, history: parsePreviewHistory(history), ...designSource === void 0 ? {} : { design: parseDesignPreview(designSource, specMatch[1]) } };
|
|
3653
3788
|
}
|
|
3654
|
-
function parsePreviewHistory(
|
|
3789
|
+
function parsePreviewHistory(source, scope = "spec") {
|
|
3655
3790
|
const records = [];
|
|
3656
3791
|
const ids = /* @__PURE__ */ new Set();
|
|
3657
|
-
|
|
3792
|
+
const shapes = scope === "spec" ? ["id,reason,requirements", "designs,id,reason,requirements"] : ["documents,id,reason,requirements"];
|
|
3793
|
+
for (const line of source.replace(/\r\n/g, "\n").split("\n")) {
|
|
3658
3794
|
if (!line.trim())
|
|
3659
3795
|
continue;
|
|
3660
3796
|
let value;
|
|
@@ -3666,10 +3802,10 @@ function parsePreviewHistory(source2) {
|
|
|
3666
3802
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3667
3803
|
fail("\uC798\uBABB\uB41C \uBCF4\uC870 \uAE30\uB85D\uC785\uB2C8\uB2E4.");
|
|
3668
3804
|
const r = value;
|
|
3669
|
-
if (!
|
|
3805
|
+
if (!shapes.includes(Object.keys(r).sort().join(",")) || typeof r.id !== "string" || !new RegExp(`^H-${token}$`).test(r.id) || ids.has(r.id) || typeof r.reason !== "string" || !r.reason.trim() || !Array.isArray(r.requirements) || !r.requirements.length && !Array.isArray(r.designs) && !Array.isArray(r.documents) || r.documents !== void 0 && (r.requirements.length || !Array.isArray(r.documents) || !r.documents.length || r.documents.some((id) => typeof id !== "string" || !/^[PG]-[a-z2-7]{10}$/.test(id)) || new Set(r.documents).size !== r.documents.length) || r.designs !== void 0 && (!Array.isArray(r.designs) || !r.designs.length || r.designs.some((id) => typeof id !== "string" || !/^S-[a-z2-7]{10}$/.test(id)) || new Set(r.designs).size !== r.designs.length) || r.requirements.some((id) => typeof id !== "string" || !reqId.test(id)) || new Set(r.requirements).size !== r.requirements.length)
|
|
3670
3806
|
fail("\uBCF4\uC870 \uAE30\uB85D\uC758 \uD544\uB4DC\xB7ID\xB7\uB300\uC0C1\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4.");
|
|
3671
3807
|
ids.add(r.id);
|
|
3672
|
-
records.push({ id: r.id, requirements: r.requirements, ...r.designs === void 0 ? {} : { designs: r.designs }, reason: r.reason });
|
|
3808
|
+
records.push({ id: r.id, requirements: r.requirements, ...r.designs === void 0 ? {} : { designs: r.designs }, ...r.documents === void 0 ? {} : { documents: r.documents }, reason: r.reason });
|
|
3673
3809
|
}
|
|
3674
3810
|
return records;
|
|
3675
3811
|
}
|
|
@@ -3743,6 +3879,50 @@ function compareSpecPreviews(before, after) {
|
|
|
3743
3879
|
});
|
|
3744
3880
|
return { specChanges, changes };
|
|
3745
3881
|
}
|
|
3882
|
+
function compareDocumentSets(before, after) {
|
|
3883
|
+
validateDocumentSets(before);
|
|
3884
|
+
validateDocumentSets(after);
|
|
3885
|
+
const index = (sets) => new Map(sets.flatMap((s) => s.documents.map((d) => [d.id, { id: d.id, title: d.title, body: d.body, specId: d.kind, path: d.path }])));
|
|
3886
|
+
const prev = index(before);
|
|
3887
|
+
const next = index(after);
|
|
3888
|
+
const previousHistory = new Map(before.flatMap((s) => s.history.map((h) => [h.id, h])));
|
|
3889
|
+
const reasons = after.flatMap((s) => s.history).filter((h) => {
|
|
3890
|
+
const old = previousHistory.get(h.id);
|
|
3891
|
+
if (old && JSON.stringify(old) !== JSON.stringify(h))
|
|
3892
|
+
fail("\uAE30\uC874 \uBCF4\uC870 \uAE30\uB85D\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + h.id);
|
|
3893
|
+
return !old;
|
|
3894
|
+
});
|
|
3895
|
+
const changes = [.../* @__PURE__ */ new Set([...prev.keys(), ...next.keys()])].sort().flatMap((id) => {
|
|
3896
|
+
const from = prev.get(id) ?? null;
|
|
3897
|
+
const to = next.get(id) ?? null;
|
|
3898
|
+
const types = [];
|
|
3899
|
+
if (!from)
|
|
3900
|
+
types.push("created");
|
|
3901
|
+
else if (!to)
|
|
3902
|
+
types.push("deleted");
|
|
3903
|
+
else {
|
|
3904
|
+
if (from.path !== to.path)
|
|
3905
|
+
types.push("moved");
|
|
3906
|
+
if (from.title !== to.title || from.body !== to.body)
|
|
3907
|
+
types.push("modified");
|
|
3908
|
+
}
|
|
3909
|
+
const kind = (to ?? from).specId;
|
|
3910
|
+
return types.length ? [{ id, types, before: from, after: to, kind, reasons: reasons.filter((h) => (h.documents ?? []).includes(id)) }] : [];
|
|
3911
|
+
});
|
|
3912
|
+
const changed = new Set(changes.map((c) => c.id));
|
|
3913
|
+
for (const h of reasons)
|
|
3914
|
+
if ((h.documents ?? []).some((id) => !changed.has(id)))
|
|
3915
|
+
fail("\uC0C8 \uBCF4\uC870 \uAE30\uB85D\uC774 \uC774\uBC88 \uBB38\uC11C \uBCC0\uACBD\uACFC \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + h.id);
|
|
3916
|
+
return changes;
|
|
3917
|
+
}
|
|
3918
|
+
function comparePreviewBundles(before, after) {
|
|
3919
|
+
const a = asBundle(before);
|
|
3920
|
+
const b = asBundle(after);
|
|
3921
|
+
validateBundle(a);
|
|
3922
|
+
validateBundle(b);
|
|
3923
|
+
const specs = compareSpecPreviews(a.specs, b.specs);
|
|
3924
|
+
return { specChanges: specs.specChanges, changes: [...specs.changes, ...compareDocumentSets(a.documents, b.documents)] };
|
|
3925
|
+
}
|
|
3746
3926
|
|
|
3747
3927
|
// ../../packages/core/dist/use-cases/edit-spec-preview.js
|
|
3748
3928
|
var fail2 = (message) => {
|
|
@@ -3769,9 +3949,11 @@ ${r.body}
|
|
|
3769
3949
|
function editSpecPreview(original, input2, generate) {
|
|
3770
3950
|
if (!Array.isArray(input2) || !input2.length || input2.length > 100)
|
|
3771
3951
|
fail2("operations\uB294 1~100\uAC1C\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
3772
|
-
const
|
|
3773
|
-
|
|
3774
|
-
const
|
|
3952
|
+
const source = asBundle(original);
|
|
3953
|
+
const specs = source.specs.map((s) => ({ ...s, requirements: s.requirements.map((r) => ({ ...r })), history: s.history.map((h) => ({ ...h, requirements: [...h.requirements] })) }));
|
|
3954
|
+
const documents = source.documents.map((set2) => ({ ...set2, documents: set2.documents.map((d) => ({ ...d })), history: set2.history.map((h) => ({ ...h })) }));
|
|
3955
|
+
validateBundle({ specs, documents });
|
|
3956
|
+
const used = /* @__PURE__ */ new Set([...specs.flatMap((s) => [s.id, ...s.requirements.map((r) => r.id)]), ...documents.flatMap((d) => d.documents.map((x) => x.id))]);
|
|
3775
3957
|
const allocate = (prefix) => {
|
|
3776
3958
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
3777
3959
|
const id = generate(prefix);
|
|
@@ -3796,7 +3978,13 @@ function editSpecPreview(original, input2, generate) {
|
|
|
3796
3978
|
move: ["type", "id", "feature"],
|
|
3797
3979
|
"rename-spec": ["type", "id", "title"],
|
|
3798
3980
|
"set-design": ["type", "feature", "title", "body"],
|
|
3799
|
-
"delete-design": ["type", "feature"]
|
|
3981
|
+
"delete-design": ["type", "feature"],
|
|
3982
|
+
"create-doc": ["type", "path", "title", "body"],
|
|
3983
|
+
"update-doc": ["type", "id", "title", "body"],
|
|
3984
|
+
"move-doc": ["type", "id", "path"],
|
|
3985
|
+
"delete-doc": ["type", "id"],
|
|
3986
|
+
"set-product": ["type", "title", "body"],
|
|
3987
|
+
"delete-product": ["type"]
|
|
3800
3988
|
};
|
|
3801
3989
|
const type = String(op.type);
|
|
3802
3990
|
const fields2 = schemas[type];
|
|
@@ -3812,10 +4000,63 @@ function editSpecPreview(original, input2, generate) {
|
|
|
3812
4000
|
fail2("\uC798\uBABB\uB41C \uD3B8\uC9D1 \uBB38\uC790: " + key);
|
|
3813
4001
|
return value;
|
|
3814
4002
|
};
|
|
3815
|
-
const
|
|
4003
|
+
const target = () => {
|
|
3816
4004
|
const path3 = previewPath(str("feature"));
|
|
3817
4005
|
return specs.find((s) => s.path === path3) ?? fail2("\uB300\uC0C1 \uBA85\uC138\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: " + path3);
|
|
3818
4006
|
};
|
|
4007
|
+
if (type === "set-product" || type === "delete-product") {
|
|
4008
|
+
const set2 = documents.find((s) => s.kind === "product");
|
|
4009
|
+
const existing = set2.documents[0];
|
|
4010
|
+
if (type === "delete-product") {
|
|
4011
|
+
if (!existing)
|
|
4012
|
+
fail2("\uC0AD\uC81C\uD560 \uC81C\uD488 \uBB38\uC11C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
4013
|
+
set2.documents = [];
|
|
4014
|
+
results.push({ type, id: existing.id });
|
|
4015
|
+
} else {
|
|
4016
|
+
const doc = { id: existing?.id ?? allocate("P"), kind: "product", path: PRODUCT_PATH, title: str("title"), body: str("body") };
|
|
4017
|
+
set2.documents = [doc];
|
|
4018
|
+
results.push({ type, id: doc.id });
|
|
4019
|
+
}
|
|
4020
|
+
continue;
|
|
4021
|
+
}
|
|
4022
|
+
if (type.endsWith("-doc")) {
|
|
4023
|
+
const all = () => documents.filter((set2) => set2.kind === "guide").flatMap((set2) => set2.documents);
|
|
4024
|
+
const documentPath = (kind, self) => {
|
|
4025
|
+
const relative = str("path");
|
|
4026
|
+
validateDocumentRelativePath(relative);
|
|
4027
|
+
const path3 = `${DOCUMENT_DIRS[kind]}/${relative}`;
|
|
4028
|
+
if (self && self.path === path3)
|
|
4029
|
+
fail2("\uC774\uBBF8 \uAC19\uC740 \uACBD\uB85C\uC785\uB2C8\uB2E4.");
|
|
4030
|
+
if (all().some((d) => d !== self && d.path.toLowerCase() === path3.toLowerCase()))
|
|
4031
|
+
fail2("\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uBB38\uC11C \uACBD\uB85C\uC785\uB2C8\uB2E4: " + path3);
|
|
4032
|
+
return path3;
|
|
4033
|
+
};
|
|
4034
|
+
const find = () => {
|
|
4035
|
+
const id = str("id");
|
|
4036
|
+
return all().find((d) => d.id === id) ?? fail2("\uC9C0\uCE68 \uBB38\uC11C ID\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: " + id);
|
|
4037
|
+
};
|
|
4038
|
+
if (type === "create-doc") {
|
|
4039
|
+
const kind = "guide";
|
|
4040
|
+
const doc = { id: allocate("G"), kind, path: documentPath(kind), title: str("title"), body: str("body") };
|
|
4041
|
+
documents.find((s) => s.kind === kind).documents.push(doc);
|
|
4042
|
+
results.push({ type, id: doc.id });
|
|
4043
|
+
} else if (type === "update-doc") {
|
|
4044
|
+
const doc = find();
|
|
4045
|
+
doc.title = str("title");
|
|
4046
|
+
doc.body = str("body");
|
|
4047
|
+
results.push({ type, id: doc.id });
|
|
4048
|
+
} else if (type === "move-doc") {
|
|
4049
|
+
const doc = find();
|
|
4050
|
+
doc.path = documentPath(doc.kind, doc);
|
|
4051
|
+
results.push({ type, id: doc.id });
|
|
4052
|
+
} else {
|
|
4053
|
+
const doc = find();
|
|
4054
|
+
const set2 = documents.find((s) => s.kind === doc.kind);
|
|
4055
|
+
set2.documents = set2.documents.filter((d) => d.id !== doc.id);
|
|
4056
|
+
results.push({ type, id: doc.id });
|
|
4057
|
+
}
|
|
4058
|
+
continue;
|
|
4059
|
+
}
|
|
3819
4060
|
if (type === "create") {
|
|
3820
4061
|
const path3 = previewPath(str("feature"));
|
|
3821
4062
|
if (specs.some((s) => s.path.toLowerCase() === path3.toLowerCase()))
|
|
@@ -3824,12 +4065,12 @@ function editSpecPreview(original, input2, generate) {
|
|
|
3824
4065
|
specs.push(spec2);
|
|
3825
4066
|
results.push({ type, id: spec2.id });
|
|
3826
4067
|
} else if (type === "add") {
|
|
3827
|
-
const spec2 =
|
|
4068
|
+
const spec2 = target();
|
|
3828
4069
|
const req = { id: allocate("R"), title: str("title"), body: str("body") };
|
|
3829
4070
|
spec2.requirements.push(req);
|
|
3830
4071
|
results.push({ type, id: req.id });
|
|
3831
4072
|
} else if (type === "set-design" || type === "delete-design") {
|
|
3832
|
-
const spec2 =
|
|
4073
|
+
const spec2 = target();
|
|
3833
4074
|
if (type === "delete-design") {
|
|
3834
4075
|
if (!spec2.design)
|
|
3835
4076
|
fail2("\uC0AD\uC81C\uD560 \uC124\uACC4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
@@ -3846,7 +4087,7 @@ function editSpecPreview(original, input2, generate) {
|
|
|
3846
4087
|
const source2 = specs.find((s) => s.requirements.some((r) => r.id === id)) ?? fail2("\uC694\uAD6C\uC0AC\uD56D ID\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
3847
4088
|
const req = source2.requirements.find((r) => r.id === id);
|
|
3848
4089
|
if (type === "move") {
|
|
3849
|
-
const destination =
|
|
4090
|
+
const destination = target();
|
|
3850
4091
|
if (source2 === destination)
|
|
3851
4092
|
fail2("\uC774\uBBF8 \uAC19\uC740 \uBA85\uC138\uC5D0 \uC18D\uD569\uB2C8\uB2E4.");
|
|
3852
4093
|
source2.requirements = source2.requirements.filter((r) => r.id !== id);
|
|
@@ -3863,8 +4104,14 @@ function editSpecPreview(original, input2, generate) {
|
|
|
3863
4104
|
if (JSON.stringify({ ...parsed, history: spec2.history }) !== JSON.stringify(spec2))
|
|
3864
4105
|
fail2("\uBCF8\uBB38\uC774 \uBA85\uC138 \uAD6C\uC870\uB97C \uBCC0\uACBD\uD569\uB2C8\uB2E4.");
|
|
3865
4106
|
}
|
|
3866
|
-
|
|
3867
|
-
|
|
4107
|
+
for (const set2 of documents) {
|
|
4108
|
+
set2.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
4109
|
+
for (const doc of set2.documents)
|
|
4110
|
+
if (JSON.stringify(parseDocument(doc.path, renderDocument(doc))) !== JSON.stringify(doc))
|
|
4111
|
+
fail2("\uBCF8\uBB38\uC774 \uBB38\uC11C \uAD6C\uC870\uB97C \uBCC0\uACBD\uD569\uB2C8\uB2E4: " + doc.path);
|
|
4112
|
+
}
|
|
4113
|
+
validateBundle({ specs, documents });
|
|
4114
|
+
return { specs, documents, results };
|
|
3868
4115
|
}
|
|
3869
4116
|
|
|
3870
4117
|
// ../../packages/core/dist/use-cases/prepare-spec-preview.js
|
|
@@ -3873,27 +4120,50 @@ var fail3 = (message) => {
|
|
|
3873
4120
|
};
|
|
3874
4121
|
var equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3875
4122
|
var historyPath = (s) => s.path.replace(/requirements\.md$/, "history.jsonl");
|
|
3876
|
-
function
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
4123
|
+
function pendingPreviewReasons(before, current) {
|
|
4124
|
+
const a = asBundle(before);
|
|
4125
|
+
const b = asBundle(current);
|
|
4126
|
+
return [
|
|
4127
|
+
...b.specs.flatMap((s) => s.history.slice(a.specs.find((p) => p.id === s.id)?.history.length ?? 0).map((h) => ({ specId: s.id, path: historyPath(s), ...h }))),
|
|
4128
|
+
...b.documents.flatMap((d) => d.history.slice(a.documents.find((p) => p.kind === d.kind)?.history.length ?? 0).map((h) => ({ kind: d.kind, path: documentHistoryPath(d.kind), ...h })))
|
|
4129
|
+
];
|
|
4130
|
+
}
|
|
4131
|
+
function finalSpecPreviewChanges(beforeValue, currentValue) {
|
|
4132
|
+
const before = asBundle(beforeValue);
|
|
4133
|
+
const current = asBundle(currentValue);
|
|
4134
|
+
validateBundle(before);
|
|
4135
|
+
validateBundle(current);
|
|
4136
|
+
for (const previous of before.specs) {
|
|
4137
|
+
const next = current.specs.find((s) => s.id === previous.id);
|
|
3881
4138
|
if (previous.history.length && !next)
|
|
3882
4139
|
fail3("\uAE30\uC874 \uC774\uC720\uB97C \uBCF4\uC874\uD560 \uBE48 \uBA85\uC138\uB97C \uB0A8\uACA8\uC8FC\uC138\uC694: " + previous.id);
|
|
3883
4140
|
if (next && !equal(next.history.slice(0, previous.history.length), previous.history))
|
|
3884
4141
|
fail3("\uCEE4\uBC0B\uB41C \uC774\uC720\uC758 \uC218\uC815\xB7\uC0AD\uC81C\xB7\uC7AC\uBC30\uCE58\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + previous.id);
|
|
3885
4142
|
}
|
|
3886
|
-
|
|
3887
|
-
|
|
4143
|
+
for (const previous of before.documents) {
|
|
4144
|
+
const next = current.documents.find((s) => s.kind === previous.kind);
|
|
4145
|
+
if (!equal(next.history.slice(0, previous.history.length), previous.history))
|
|
4146
|
+
fail3("\uCEE4\uBC0B\uB41C \uBB38\uC11C \uC774\uC720\uC758 \uC218\uC815\xB7\uC0AD\uC81C\xB7\uC7AC\uBC30\uCE58\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + previous.kind);
|
|
4147
|
+
}
|
|
4148
|
+
const clean = {
|
|
4149
|
+
specs: current.specs.map((s) => ({ ...s, history: before.specs.find((p) => p.id === s.id)?.history ?? [] })),
|
|
4150
|
+
documents: current.documents.map((d) => ({ ...d, history: before.documents.find((p) => p.kind === d.kind)?.history ?? [] }))
|
|
4151
|
+
};
|
|
4152
|
+
return comparePreviewBundles(before, clean);
|
|
3888
4153
|
}
|
|
3889
|
-
function prepareSpecPreview(
|
|
3890
|
-
const
|
|
4154
|
+
function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles, input2, generate) {
|
|
4155
|
+
const beforeBundle = asBundle(beforeValue);
|
|
4156
|
+
const currentBundle = asBundle(currentValue);
|
|
4157
|
+
const before = beforeBundle.specs;
|
|
4158
|
+
const current = currentBundle.specs;
|
|
4159
|
+
const delta = finalSpecPreviewChanges(beforeBundle, currentBundle);
|
|
3891
4160
|
if (!Array.isArray(input2) || input2.length > 100)
|
|
3892
4161
|
fail3("reasons\uB294 \uCD5C\uB300 100\uAC1C\uC758 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
|
|
3893
4162
|
const writes = /* @__PURE__ */ new Map();
|
|
3894
4163
|
const pending = /* @__PURE__ */ new Map();
|
|
3895
4164
|
const covered = /* @__PURE__ */ new Set();
|
|
3896
|
-
const used = new Set([...before, ...current
|
|
4165
|
+
const used = new Set([...before.flatMap((s) => s.history), ...current.flatMap((s) => s.history), ...beforeBundle.documents.flatMap((d) => d.history), ...currentBundle.documents.flatMap((d) => d.history)].map((h) => h.id));
|
|
4166
|
+
const documentPending = /* @__PURE__ */ new Map();
|
|
3897
4167
|
const allocate = () => {
|
|
3898
4168
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
3899
4169
|
const id = generate();
|
|
@@ -3910,10 +4180,32 @@ function prepareSpecPreview(before, current, baseFiles, currentFiles, input2, ge
|
|
|
3910
4180
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3911
4181
|
fail3("\uC798\uBABB\uB41C \uC774\uC720 \uC785\uB825\uC785\uB2C8\uB2E4.");
|
|
3912
4182
|
const raw = value;
|
|
3913
|
-
|
|
4183
|
+
const shape = Object.keys(raw).sort().join(",");
|
|
4184
|
+
if (!["reason,requirements", "designs,reason,requirements", "documents,reason,requirements"].includes(shape) || typeof raw.reason !== "string" || !raw.reason.trim() || raw.reason.includes("\0") || raw.reason.length > 16e3 || !Array.isArray(raw.requirements) || raw.designs !== void 0 && !Array.isArray(raw.designs) || raw.documents !== void 0 && !Array.isArray(raw.documents) || !(raw.requirements.length || raw.designs?.length || raw.documents?.length))
|
|
3914
4185
|
fail3("\uC774\uC720\uC640 \uB300\uC0C1 \uC694\uAD6C\uC0AC\uD56D\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
3915
4186
|
const reason = raw.reason.trim();
|
|
3916
4187
|
const groups = /* @__PURE__ */ new Map();
|
|
4188
|
+
if (raw.documents !== void 0) {
|
|
4189
|
+
if (raw.requirements.length)
|
|
4190
|
+
fail3("\uBB38\uC11C \uC774\uC720\uC5D0\uB294 \uC694\uAD6C\uC0AC\uD56D\uC744 \uD568\uAED8 \uC9C0\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
4191
|
+
const ids = raw.documents;
|
|
4192
|
+
let kind;
|
|
4193
|
+
for (const value2 of ids) {
|
|
4194
|
+
if (typeof value2 !== "string" || !/^[PG]-[a-z2-7]{10}$/.test(value2) || covered.has(value2))
|
|
4195
|
+
return fail3("\uBB38\uC11C \uC774\uC720 \uB300\uC0C1\uC774 \uC798\uBABB\uB410\uAC70\uB098 \uC911\uBCF5\uB429\uB2C8\uB2E4.");
|
|
4196
|
+
const change2 = delta.changes.find((c) => c.id === value2) ?? fail3("\uCD5C\uC885 \uBCC0\uACBD\uC774 \uC5C6\uB294 \uBB38\uC11C\uC5D0 \uC774\uC720\uB97C \uBD99\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: " + value2);
|
|
4197
|
+
if (kind && change2.kind !== kind)
|
|
4198
|
+
fail3("\uD55C \uC774\uC720\uB294 \uD55C \uC885\uB958\uC758 \uBB38\uC11C\uB9CC \uB300\uC0C1\uC73C\uB85C \uD569\uB2C8\uB2E4.");
|
|
4199
|
+
kind = change2.kind;
|
|
4200
|
+
covered.add(value2);
|
|
4201
|
+
}
|
|
4202
|
+
const documents = [...ids].sort();
|
|
4203
|
+
const set2 = currentBundle.documents.find((d) => d.kind === kind);
|
|
4204
|
+
const oldCount = beforeBundle.documents.find((d) => d.kind === kind)?.history.length ?? 0;
|
|
4205
|
+
const reusable = set2.history.slice(oldCount).find((h) => h.reason === reason && equal([...h.documents ?? []].sort(), documents));
|
|
4206
|
+
documentPending.set(kind, [...documentPending.get(kind) ?? [], { id: reusable?.id ?? allocate(), requirements: [], documents, reason }]);
|
|
4207
|
+
continue;
|
|
4208
|
+
}
|
|
3917
4209
|
for (const value2 of [...raw.requirements, ...raw.designs ?? []]) {
|
|
3918
4210
|
if (typeof value2 !== "string")
|
|
3919
4211
|
fail3("\uC798\uBABB\uB41C \uC774\uC720 \uB300\uC0C1\uC785\uB2C8\uB2E4.");
|
|
@@ -3951,11 +4243,22 @@ function prepareSpecPreview(before, current, baseFiles, currentFiles, input2, ge
|
|
|
3951
4243
|
if ((currentFiles.get(path3) ?? null) !== next)
|
|
3952
4244
|
writes.set(path3, next);
|
|
3953
4245
|
}
|
|
4246
|
+
for (const set2 of currentBundle.documents) {
|
|
4247
|
+
const path3 = documentHistoryPath(set2.kind);
|
|
4248
|
+
const base = baseFiles.get(path3);
|
|
4249
|
+
const records = (documentPending.get(set2.kind) ?? []).sort((a, b) => a.documents[0] < b.documents[0] ? -1 : 1);
|
|
4250
|
+
let text2 = base ?? "";
|
|
4251
|
+
if (records.length)
|
|
4252
|
+
text2 += (text2 && !text2.endsWith("\n") ? "\n" : "") + records.map((h) => JSON.stringify(h) + "\n").join("");
|
|
4253
|
+
const next = base !== void 0 || records.length ? text2 : null;
|
|
4254
|
+
if ((currentFiles.get(path3) ?? null) !== next)
|
|
4255
|
+
writes.set(path3, next);
|
|
4256
|
+
}
|
|
3954
4257
|
return {
|
|
3955
4258
|
writes,
|
|
3956
4259
|
changes: delta.changes,
|
|
3957
4260
|
specChanges: delta.specChanges,
|
|
3958
|
-
reasons: [...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))),
|
|
4261
|
+
reasons: [...[...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))), ...[...documentPending].flatMap(([kind, records]) => records.map((record3) => ({ kind, ...record3 })))],
|
|
3959
4262
|
withoutReason: delta.changes.filter((c) => !covered.has(c.id)).map((c) => c.id)
|
|
3960
4263
|
};
|
|
3961
4264
|
}
|
|
@@ -4908,10 +5211,10 @@ function cached(getter) {
|
|
|
4908
5211
|
function nullish(input2) {
|
|
4909
5212
|
return input2 === null || input2 === void 0;
|
|
4910
5213
|
}
|
|
4911
|
-
function cleanRegex(
|
|
4912
|
-
const start =
|
|
4913
|
-
const end =
|
|
4914
|
-
return
|
|
5214
|
+
function cleanRegex(source) {
|
|
5215
|
+
const start = source.startsWith("^") ? 1 : 0;
|
|
5216
|
+
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
5217
|
+
return source.slice(start, end);
|
|
4915
5218
|
}
|
|
4916
5219
|
function floatSafeRemainder(val, step) {
|
|
4917
5220
|
const ratio = val / step;
|
|
@@ -4947,8 +5250,8 @@ function defineLazy(object4, key, getter) {
|
|
|
4947
5250
|
function objectClone(obj) {
|
|
4948
5251
|
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
|
|
4949
5252
|
}
|
|
4950
|
-
function assignProp(
|
|
4951
|
-
Object.defineProperty(
|
|
5253
|
+
function assignProp(target, prop, value) {
|
|
5254
|
+
Object.defineProperty(target, prop, {
|
|
4952
5255
|
value,
|
|
4953
5256
|
writable: true,
|
|
4954
5257
|
enumerable: true,
|
|
@@ -4962,8 +5265,8 @@ function rawShape(def) {
|
|
|
4962
5265
|
function sourceShape(schema) {
|
|
4963
5266
|
return rawShape(schema._zod.def) ?? schema._zod.def.shape;
|
|
4964
5267
|
}
|
|
4965
|
-
function deferProp(
|
|
4966
|
-
Object.defineProperty(
|
|
5268
|
+
function deferProp(target, key, getter) {
|
|
5269
|
+
Object.defineProperty(target, key, {
|
|
4967
5270
|
get() {
|
|
4968
5271
|
const value = getter();
|
|
4969
5272
|
assignProp(this, key, value);
|
|
@@ -4973,36 +5276,36 @@ function deferProp(target2, key, getter) {
|
|
|
4973
5276
|
configurable: true
|
|
4974
5277
|
});
|
|
4975
5278
|
}
|
|
4976
|
-
function putProp(
|
|
4977
|
-
if (key in
|
|
4978
|
-
assignProp(
|
|
5279
|
+
function putProp(target, key, value) {
|
|
5280
|
+
if (key in target)
|
|
5281
|
+
assignProp(target, key, value);
|
|
4979
5282
|
else
|
|
4980
|
-
|
|
5283
|
+
target[key] = value;
|
|
4981
5284
|
}
|
|
4982
|
-
function mirrorShape(
|
|
4983
|
-
const raw = sourceShape(
|
|
5285
|
+
function mirrorShape(target, source, keys2, wrap) {
|
|
5286
|
+
const raw = sourceShape(source);
|
|
4984
5287
|
for (const key of keys2) {
|
|
4985
5288
|
const desc = Object.getOwnPropertyDescriptor(raw, key);
|
|
4986
5289
|
if (!desc.enumerable)
|
|
4987
5290
|
continue;
|
|
4988
5291
|
if (desc.get) {
|
|
4989
|
-
deferProp(
|
|
4990
|
-
const value =
|
|
5292
|
+
deferProp(target, key, () => {
|
|
5293
|
+
const value = source._zod.def.shape[key];
|
|
4991
5294
|
return wrap ? wrap(value, key) : value;
|
|
4992
5295
|
});
|
|
4993
5296
|
} else
|
|
4994
|
-
putProp(
|
|
5297
|
+
putProp(target, key, wrap ? wrap(desc.value, key) : desc.value);
|
|
4995
5298
|
}
|
|
4996
5299
|
}
|
|
4997
|
-
function mirrorProps(
|
|
4998
|
-
for (const key of Reflect.ownKeys(
|
|
4999
|
-
const desc = Object.getOwnPropertyDescriptor(
|
|
5300
|
+
function mirrorProps(target, source) {
|
|
5301
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
5302
|
+
const desc = Object.getOwnPropertyDescriptor(source, key);
|
|
5000
5303
|
if (!desc.enumerable)
|
|
5001
5304
|
continue;
|
|
5002
5305
|
if (desc.get)
|
|
5003
|
-
deferProp(
|
|
5306
|
+
deferProp(target, key, () => source[key]);
|
|
5004
5307
|
else
|
|
5005
|
-
putProp(
|
|
5308
|
+
putProp(target, key, desc.value);
|
|
5006
5309
|
}
|
|
5007
5310
|
}
|
|
5008
5311
|
function mergeDefs(...defs) {
|
|
@@ -5181,35 +5484,35 @@ function normalizeParams(_params) {
|
|
|
5181
5484
|
return params;
|
|
5182
5485
|
}
|
|
5183
5486
|
function createTransparentProxy(getter) {
|
|
5184
|
-
let
|
|
5487
|
+
let target;
|
|
5185
5488
|
return new Proxy({}, {
|
|
5186
5489
|
get(_, prop, receiver) {
|
|
5187
|
-
|
|
5188
|
-
return Reflect.get(
|
|
5490
|
+
target ?? (target = getter());
|
|
5491
|
+
return Reflect.get(target, prop, receiver);
|
|
5189
5492
|
},
|
|
5190
5493
|
set(_, prop, value, receiver) {
|
|
5191
|
-
|
|
5192
|
-
return Reflect.set(
|
|
5494
|
+
target ?? (target = getter());
|
|
5495
|
+
return Reflect.set(target, prop, value, receiver);
|
|
5193
5496
|
},
|
|
5194
5497
|
has(_, prop) {
|
|
5195
|
-
|
|
5196
|
-
return Reflect.has(
|
|
5498
|
+
target ?? (target = getter());
|
|
5499
|
+
return Reflect.has(target, prop);
|
|
5197
5500
|
},
|
|
5198
5501
|
deleteProperty(_, prop) {
|
|
5199
|
-
|
|
5200
|
-
return Reflect.deleteProperty(
|
|
5502
|
+
target ?? (target = getter());
|
|
5503
|
+
return Reflect.deleteProperty(target, prop);
|
|
5201
5504
|
},
|
|
5202
5505
|
ownKeys(_) {
|
|
5203
|
-
|
|
5204
|
-
return Reflect.ownKeys(
|
|
5506
|
+
target ?? (target = getter());
|
|
5507
|
+
return Reflect.ownKeys(target);
|
|
5205
5508
|
},
|
|
5206
5509
|
getOwnPropertyDescriptor(_, prop) {
|
|
5207
|
-
|
|
5208
|
-
return Reflect.getOwnPropertyDescriptor(
|
|
5510
|
+
target ?? (target = getter());
|
|
5511
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
5209
5512
|
},
|
|
5210
5513
|
defineProperty(_, prop, descriptor) {
|
|
5211
|
-
|
|
5212
|
-
return Reflect.defineProperty(
|
|
5514
|
+
target ?? (target = getter());
|
|
5515
|
+
return Reflect.defineProperty(target, prop, descriptor);
|
|
5213
5516
|
}
|
|
5214
5517
|
});
|
|
5215
5518
|
}
|
|
@@ -5680,10 +5983,10 @@ function $constructor(name, initializer3, proto, params) {
|
|
|
5680
5983
|
let up = own2;
|
|
5681
5984
|
while (up && up !== ctorProto)
|
|
5682
5985
|
up = Object.getPrototypeOf(up);
|
|
5683
|
-
const
|
|
5684
|
-
if (!initialized.has(
|
|
5685
|
-
initialized.add(
|
|
5686
|
-
members(
|
|
5986
|
+
const target = up ?? own2;
|
|
5987
|
+
if (!initialized.has(target)) {
|
|
5988
|
+
initialized.add(target);
|
|
5989
|
+
members(target, protoMembers);
|
|
5687
5990
|
}
|
|
5688
5991
|
}
|
|
5689
5992
|
const proto2 = _.prototype;
|
|
@@ -6213,8 +6516,8 @@ var creditCard = /^\d(?:[ -]?\d){11,18}$/;
|
|
|
6213
6516
|
var currencyCode = /^(?:AED|AFN|ALL|AMD|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLE|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VED|VES|VND|VUV|WST|XAD|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XCG|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWG)$/;
|
|
6214
6517
|
var iban = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
|
|
6215
6518
|
var dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
|
|
6216
|
-
function anchor(
|
|
6217
|
-
return new RegExp(`^${
|
|
6519
|
+
function anchor(source) {
|
|
6520
|
+
return new RegExp(`^${source}$`);
|
|
6218
6521
|
}
|
|
6219
6522
|
var date = /* @__PURE__ */ anchor(dateSource);
|
|
6220
6523
|
function timeSource(args) {
|
|
@@ -9037,11 +9340,11 @@ var $ZodTemplateLiteral = /* @__PURE__ */ $constructor("$ZodTemplateLiteral", (i
|
|
|
9037
9340
|
const regexParts = [];
|
|
9038
9341
|
for (const part of def.parts) {
|
|
9039
9342
|
if (typeof part === "object" && part !== null) {
|
|
9040
|
-
const
|
|
9041
|
-
if (!
|
|
9343
|
+
const source = partPattern(part);
|
|
9344
|
+
if (!source) {
|
|
9042
9345
|
throw new Error(`Invalid template literal part, no pattern found: ${[...part._zod.traits].shift()}`);
|
|
9043
9346
|
}
|
|
9044
|
-
regexParts.push(cleanRegex(
|
|
9347
|
+
regexParts.push(cleanRegex(source));
|
|
9045
9348
|
} else if (part === null || primitiveTypes.has(typeof part)) {
|
|
9046
9349
|
regexParts.push(escapeRegex(`${part}`));
|
|
9047
9350
|
} else {
|
|
@@ -17167,9 +17470,9 @@ function withParser(schema, parser) {
|
|
|
17167
17470
|
installCompiledUserMethods(clone2, schema, parser);
|
|
17168
17471
|
return clone2;
|
|
17169
17472
|
}
|
|
17170
|
-
function installCompiledUserMethods(
|
|
17171
|
-
const targetAny =
|
|
17172
|
-
const sourceAny =
|
|
17473
|
+
function installCompiledUserMethods(target, source, parser) {
|
|
17474
|
+
const targetAny = target;
|
|
17475
|
+
const sourceAny = source;
|
|
17173
17476
|
if (typeof sourceAny.safeParse === "function") {
|
|
17174
17477
|
const originalSafeParse = sourceAny.safeParse;
|
|
17175
17478
|
targetAny.safeParse = (data, params) => {
|
|
@@ -19763,26 +20066,26 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
|
|
|
19763
20066
|
}
|
|
19764
20067
|
|
|
19765
20068
|
// ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/to-json-schema.js
|
|
19766
|
-
function assignProps(
|
|
19767
|
-
for (const
|
|
19768
|
-
for (const key of Reflect.ownKeys(
|
|
19769
|
-
if (Object.prototype.propertyIsEnumerable.call(
|
|
19770
|
-
assignProp(
|
|
20069
|
+
function assignProps(target, ...sources) {
|
|
20070
|
+
for (const source of sources) {
|
|
20071
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
20072
|
+
if (Object.prototype.propertyIsEnumerable.call(source, key)) {
|
|
20073
|
+
assignProp(target, key, source[key]);
|
|
19771
20074
|
}
|
|
19772
20075
|
}
|
|
19773
20076
|
}
|
|
19774
|
-
return
|
|
20077
|
+
return target;
|
|
19775
20078
|
}
|
|
19776
20079
|
function initializeContext(params) {
|
|
19777
|
-
let
|
|
19778
|
-
if (
|
|
19779
|
-
|
|
19780
|
-
if (
|
|
19781
|
-
|
|
20080
|
+
let target = params?.target ?? "draft-2020-12";
|
|
20081
|
+
if (target === "draft-4")
|
|
20082
|
+
target = "draft-04";
|
|
20083
|
+
if (target === "draft-7")
|
|
20084
|
+
target = "draft-07";
|
|
19782
20085
|
return {
|
|
19783
20086
|
processors: params.processors ?? {},
|
|
19784
20087
|
metadataRegistry: params?.metadata ?? globalRegistry,
|
|
19785
|
-
target
|
|
20088
|
+
target,
|
|
19786
20089
|
unrepresentable: params?.unrepresentable ?? "throw",
|
|
19787
20090
|
override: params?.override ?? (() => {
|
|
19788
20091
|
}),
|
|
@@ -20285,8 +20588,8 @@ var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
|
|
|
20285
20588
|
return finalize(ctx, schema);
|
|
20286
20589
|
};
|
|
20287
20590
|
var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
|
|
20288
|
-
const { libraryOptions, target
|
|
20289
|
-
const ctx = initializeContext({ ...libraryOptions ?? {}, target
|
|
20591
|
+
const { libraryOptions, target } = params ?? {};
|
|
20592
|
+
const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
|
|
20290
20593
|
processSchema(schema, ctx);
|
|
20291
20594
|
extractDefs(ctx, schema);
|
|
20292
20595
|
return finalize(ctx, schema);
|
|
@@ -21480,8 +21783,8 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
21480
21783
|
catch(params) {
|
|
21481
21784
|
return _catch2(this, params);
|
|
21482
21785
|
},
|
|
21483
|
-
pipe(
|
|
21484
|
-
return pipe(this,
|
|
21786
|
+
pipe(target) {
|
|
21787
|
+
return pipe(this, target);
|
|
21485
21788
|
},
|
|
21486
21789
|
readonly() {
|
|
21487
21790
|
return readonly(this);
|
|
@@ -23990,21 +24293,23 @@ var browserHttpErrorV1 = external_exports.strictObject({
|
|
|
23990
24293
|
})
|
|
23991
24294
|
});
|
|
23992
24295
|
|
|
23993
|
-
// ../../packages/contracts/dist/versions/
|
|
23994
|
-
var
|
|
24296
|
+
// ../../packages/contracts/dist/versions/project-init-v4.js
|
|
24297
|
+
var projectInitV4 = external_exports.discriminatedUnion("ok", [
|
|
23995
24298
|
external_exports.strictObject({
|
|
23996
|
-
contract: external_exports.literal("
|
|
23997
|
-
version: external_exports.literal(
|
|
24299
|
+
contract: external_exports.literal("project-init"),
|
|
24300
|
+
version: external_exports.literal(4),
|
|
23998
24301
|
ok: external_exports.literal(true),
|
|
23999
|
-
outcome: external_exports.enum(["planned", "
|
|
24302
|
+
outcome: external_exports.enum(["planned", "created", "already-initialized"]),
|
|
24000
24303
|
rootPath: external_exports.string(),
|
|
24001
|
-
|
|
24002
|
-
|
|
24003
|
-
|
|
24004
|
-
|
|
24005
|
-
|
|
24304
|
+
configPath: external_exports.literal(".gitifact/config.json"),
|
|
24305
|
+
schemaVersion: external_exports.literal(1),
|
|
24306
|
+
baseline: external_exports.discriminatedUnion("kind", [
|
|
24307
|
+
external_exports.strictObject({ kind: external_exports.literal("empty") }),
|
|
24308
|
+
external_exports.strictObject({ kind: external_exports.literal("commit"), objectFormat: external_exports.enum(["sha1", "sha256"]), commit: external_exports.string() })
|
|
24309
|
+
]),
|
|
24310
|
+
agentDocs: external_exports.strictObject({ mode: external_exports.enum(["install", "remove", "skip"]), paths: external_exports.array(external_exports.string()) })
|
|
24006
24311
|
}),
|
|
24007
|
-
external_exports.strictObject({ contract: external_exports.literal("
|
|
24312
|
+
external_exports.strictObject({ contract: external_exports.literal("project-init"), version: external_exports.literal(4), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
|
|
24008
24313
|
]);
|
|
24009
24314
|
|
|
24010
24315
|
// ../../packages/contracts/dist/versions/browser-specs-v1.js
|
|
@@ -24022,6 +24327,7 @@ var feature = external_exports.strictObject({
|
|
|
24022
24327
|
updatedAt: external_exports.string().nullable()
|
|
24023
24328
|
});
|
|
24024
24329
|
var snapshot = requirement.extend({ specId: external_exports.string(), path: external_exports.string() }).nullable();
|
|
24330
|
+
var document = external_exports.strictObject({ id: external_exports.string(), kind: external_exports.enum(["product", "guide"]), path: external_exports.string(), title: external_exports.string(), body: external_exports.string(), updatedAt: external_exports.string().nullable() });
|
|
24025
24331
|
var browserSpecsV1 = external_exports.strictObject({
|
|
24026
24332
|
contract: external_exports.literal("browser-specs"),
|
|
24027
24333
|
version: external_exports.literal(1),
|
|
@@ -24030,6 +24336,7 @@ var browserSpecsV1 = external_exports.strictObject({
|
|
|
24030
24336
|
observedAt: external_exports.string(),
|
|
24031
24337
|
working: external_exports.boolean(),
|
|
24032
24338
|
features: external_exports.array(feature),
|
|
24339
|
+
documents: external_exports.array(document).default([]),
|
|
24033
24340
|
events: external_exports.array(external_exports.strictObject({
|
|
24034
24341
|
key: external_exports.string(),
|
|
24035
24342
|
commit: external_exports.string(),
|
|
@@ -24039,7 +24346,7 @@ var browserSpecsV1 = external_exports.strictObject({
|
|
|
24039
24346
|
committer: external_exports.string(),
|
|
24040
24347
|
message: external_exports.string(),
|
|
24041
24348
|
id: external_exports.string(),
|
|
24042
|
-
kind: external_exports.enum(["requirement", "design"]).optional(),
|
|
24349
|
+
kind: external_exports.enum(["requirement", "design", "product", "guide"]).optional(),
|
|
24043
24350
|
types: external_exports.array(external_exports.enum(["created", "modified", "moved", "deleted"])),
|
|
24044
24351
|
before: snapshot,
|
|
24045
24352
|
after: snapshot,
|
|
@@ -24117,6 +24424,8 @@ async function runStatus(format) {
|
|
|
24117
24424
|
import { createServer } from "node:http";
|
|
24118
24425
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24119
24426
|
import { fileURLToPath } from "node:url";
|
|
24427
|
+
import { lstat as lstat3, readFile as readFile4 } from "node:fs/promises";
|
|
24428
|
+
import { join as join4 } from "node:path";
|
|
24120
24429
|
|
|
24121
24430
|
// src/server/assets.ts
|
|
24122
24431
|
import { readdir, readFile } from "node:fs/promises";
|
|
@@ -24146,6 +24455,9 @@ function contentType(path3) {
|
|
|
24146
24455
|
if (path3.endsWith(".svg")) return "image/svg+xml";
|
|
24147
24456
|
if (path3.endsWith(".woff2")) return "font/woff2";
|
|
24148
24457
|
if (path3.endsWith(".png")) return "image/png";
|
|
24458
|
+
if (/\.jpe?g$/.test(path3)) return "image/jpeg";
|
|
24459
|
+
if (path3.endsWith(".gif")) return "image/gif";
|
|
24460
|
+
if (path3.endsWith(".webp")) return "image/webp";
|
|
24149
24461
|
return "application/octet-stream";
|
|
24150
24462
|
}
|
|
24151
24463
|
|
|
@@ -24206,6 +24518,7 @@ function commandScoped(key, load) {
|
|
|
24206
24518
|
}
|
|
24207
24519
|
|
|
24208
24520
|
// src/adapters/git/spec-preview-reader.ts
|
|
24521
|
+
var RECORD_PATHSPECS = [...STORE_DIRS.map((d) => d + "/spec/"), ...Object.values(DOCUMENT_DIRS).map((d) => d + "/")];
|
|
24209
24522
|
function specPreviewReader(cwd) {
|
|
24210
24523
|
for (const key of Object.keys(process.env)) if (/^GIT_(DIR|WORK_TREE|COMMON_DIR|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|CONFIG_COUNT|CONFIG_PARAMETERS|CONFIG_KEY_\d+|CONFIG_VALUE_\d+)$/i.test(key)) {
|
|
24211
24524
|
throw new SpecPreviewError("Git \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uB36E\uC5B4\uC4F0\uB294 \uD658\uACBD\uC5D0\uC11C\uB294 \uC870\uD68C\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
@@ -24229,14 +24542,17 @@ function specPreviewReader(cwd) {
|
|
|
24229
24542
|
};
|
|
24230
24543
|
return {
|
|
24231
24544
|
async index() {
|
|
24232
|
-
const output2 = decode5(await git(["ls-files", "--stage", "-z", "--", ...
|
|
24545
|
+
const output2 = decode5(await git(["ls-files", "--stage", "-z", "--", ...RECORD_PATHSPECS]));
|
|
24233
24546
|
const files = /* @__PURE__ */ new Map();
|
|
24234
24547
|
for (const row of output2.split("\0").filter(Boolean)) {
|
|
24235
24548
|
const match = /^(\d+) ([a-f0-9]+) ([0-3])\t([\s\S]+)$/.exec(row);
|
|
24236
24549
|
if (!match) throw new SpecPreviewError("\uC798\uBABB\uB41C index \uD56D\uBAA9\uC785\uB2C8\uB2E4.");
|
|
24237
24550
|
const [, mode, oid2, stage, path3] = match;
|
|
24238
24551
|
if (!["100644", "100755"].includes(mode) || stage !== "0") throw new SpecPreviewError("\uBA85\uC138 staging\uC5D0 \uCDA9\uB3CC \uB610\uB294 \uB9C1\uD06C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
24239
|
-
if (!recordPathPattern.test(path3))
|
|
24552
|
+
if (!recordPathPattern.test(path3)) {
|
|
24553
|
+
if (/^\.gitifact\/(?:product|guides)\//.test(path3)) continue;
|
|
24554
|
+
throw new SpecPreviewError("\uBA85\uC138 staging\uC5D0 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
24555
|
+
}
|
|
24240
24556
|
files.set(path3, oid2);
|
|
24241
24557
|
}
|
|
24242
24558
|
return files;
|
|
@@ -24275,7 +24591,7 @@ function specPreviewReader(cwd) {
|
|
|
24275
24591
|
return oid2;
|
|
24276
24592
|
},
|
|
24277
24593
|
async files(oid2, allowLegacyBaseline = false) {
|
|
24278
|
-
const entries = decode5(await git(["ls-tree", "--full-tree", "-r", "-z", oid2, "--", ...
|
|
24594
|
+
const entries = decode5(await git(["ls-tree", "--full-tree", "-r", "-z", oid2, "--", ...RECORD_PATHSPECS]));
|
|
24279
24595
|
const files = /* @__PURE__ */ new Map();
|
|
24280
24596
|
let legacy;
|
|
24281
24597
|
for (const row of entries.split("\0").filter(Boolean)) {
|
|
@@ -24283,7 +24599,7 @@ function specPreviewReader(cwd) {
|
|
|
24283
24599
|
if (!match) throw new SpecPreviewError("\uC798\uBABB\uB41C Git tree\uC785\uB2C8\uB2E4.");
|
|
24284
24600
|
const [, mode, type, object4, path3] = match;
|
|
24285
24601
|
if (!["100644", "100755"].includes(mode) || type !== "blob") throw new SpecPreviewError("\uBA85\uC138 \uC601\uC5ED\uC5D0 \uB9C1\uD06C \uB610\uB294 \uC11C\uBE0C\uBAA8\uB4C8\uC774 \uC788\uC2B5\uB2C8\uB2E4: " + path3);
|
|
24286
|
-
if (
|
|
24602
|
+
if (!recordPathPattern.test(path3) && !path3.endsWith("/tryce.json")) continue;
|
|
24287
24603
|
if (path3.endsWith("/tryce.json")) {
|
|
24288
24604
|
if (!allowLegacyBaseline) throw new SpecPreviewError("\uAE30\uC874 JSON \uD615\uC2DD\uC740 req \uBA85\uB839\uC73C\uB85C \uC870\uD68C\uD558\uC138\uC694. \uC774 \uBA85\uB839\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
24289
24605
|
legacy = path3.split("/")[0];
|
|
@@ -24322,6 +24638,9 @@ function specPreviewReader(cwd) {
|
|
|
24322
24638
|
async read(oid2) {
|
|
24323
24639
|
const files = await this.files(oid2);
|
|
24324
24640
|
return parsePreviewFiles(files);
|
|
24641
|
+
},
|
|
24642
|
+
async readBundle(oid2) {
|
|
24643
|
+
return parsePreviewBundle(await this.files(oid2));
|
|
24325
24644
|
}
|
|
24326
24645
|
};
|
|
24327
24646
|
}
|
|
@@ -24432,18 +24751,18 @@ async function snapshot2(root) {
|
|
|
24432
24751
|
if (!parent && await info(join3(root, ".tryce"))) fail4(".tryce \uC800\uC7A5\uC18C\uC785\uB2C8\uB2E4. gitifact migrate\uB85C .gitifact\uC5D0 \uC804\uD658\uD55C \uB4A4 \uC0AC\uC6A9\uD558\uC138\uC694.");
|
|
24433
24752
|
const config2 = await readConfigFile(root);
|
|
24434
24753
|
if (config2 !== void 0 && !("schemaVersion" in parseManagedConfig(config2)) || await info(join3(root, "specs"))) fail4("\uAE30\uC874 \uD504\uB85C\uC81D\uD2B8 \uD615\uC2DD\uC740 \uBCC4\uB3C4 \uC804\uD658\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
24435
|
-
async function visit2(path3, depth) {
|
|
24754
|
+
async function visit2(path3, depth, documents) {
|
|
24436
24755
|
const stat3 = await info(join3(root, path3));
|
|
24437
24756
|
if (!stat3) return;
|
|
24438
24757
|
if (stat3.isSymbolicLink() || !stat3.isDirectory() && !stat3.isFile()) fail4("\uB9C1\uD06C\xB7\uD2B9\uC218 \uD30C\uC77C\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + path3);
|
|
24439
24758
|
if (++count > 4e3) fail4("\uBA85\uC138 \uD30C\uC77C \uC218 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
24440
24759
|
if (stat3.isDirectory()) {
|
|
24441
|
-
if (depth > 1) fail4("\uC911\uCCA9 \
|
|
24442
|
-
for (const name of (await readdir2(join3(root, path3))).sort()) await visit2(path3 + "/" + name, depth + 1);
|
|
24760
|
+
if (depth > (documents ? 8 : 1)) fail4("\uC911\uCCA9 \uD3F4\uB354 \uAE4A\uC774 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4: " + path3);
|
|
24761
|
+
for (const name of (await readdir2(join3(root, path3))).sort()) await visit2(path3 + "/" + name, depth + 1, documents);
|
|
24443
24762
|
} else {
|
|
24444
24763
|
if (path3.endsWith("/tryce.json")) fail4("\uAE30\uC874 JSON \uD615\uC2DD\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
24445
|
-
if (!/\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) return;
|
|
24446
|
-
if (
|
|
24764
|
+
if (documents ? !(path3.endsWith(".md") || depth === 1 && path3.endsWith("/history.jsonl")) : !/\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) return;
|
|
24765
|
+
if (!recordPathPattern.test(path3) || path3.startsWith(".tryce/")) fail4("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uACBD\uB85C\uC785\uB2C8\uB2E4: " + path3);
|
|
24447
24766
|
if (stat3.nlink !== 1 || stat3.size > 1024 * 1024) fail4("\uD558\uB4DC \uB9C1\uD06C \uB610\uB294 1 MiB \uD55C\uB3C4\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
24448
24767
|
const raw = await readFile3(join3(root, path3));
|
|
24449
24768
|
bytes += raw.length;
|
|
@@ -24451,9 +24770,10 @@ async function snapshot2(root) {
|
|
|
24451
24770
|
files.set(path3, decode3(raw));
|
|
24452
24771
|
}
|
|
24453
24772
|
}
|
|
24454
|
-
await visit2(".gitifact/spec", 0);
|
|
24455
|
-
const
|
|
24456
|
-
|
|
24773
|
+
await visit2(".gitifact/spec", 0, false);
|
|
24774
|
+
for (const dir of Object.values(DOCUMENT_DIRS)) await visit2(dir, 0, true);
|
|
24775
|
+
const bundle = parsePreviewBundle(files);
|
|
24776
|
+
return { files, specs: bundle.specs, documents: bundle.documents, bundle, config: config2, stamp: digest(JSON.stringify([...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (config2 ?? "")) };
|
|
24457
24777
|
}
|
|
24458
24778
|
async function readWorkingPreviewState(cwd) {
|
|
24459
24779
|
const { root, gitDir } = await specPreviewReader(cwd).location();
|
|
@@ -24466,21 +24786,29 @@ async function readWorkingPreviewState(cwd) {
|
|
|
24466
24786
|
return first;
|
|
24467
24787
|
}
|
|
24468
24788
|
async function readWorkingPreview(cwd) {
|
|
24469
|
-
const { stamp, specs } = await readWorkingPreviewState(cwd);
|
|
24470
|
-
return { stamp, specs, warnings: designReferenceWarnings(specs) };
|
|
24789
|
+
const { stamp, specs, documents } = await readWorkingPreviewState(cwd);
|
|
24790
|
+
return { stamp, specs, documents, warnings: designReferenceWarnings(specs) };
|
|
24471
24791
|
}
|
|
24472
24792
|
async function saveWorkingPreview(cwd, input2, publish = rename) {
|
|
24473
24793
|
if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) fail4("\uC800\uC7A5 \uC785\uB825\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4.");
|
|
24474
24794
|
const request = input2;
|
|
24475
24795
|
if (Object.keys(request).sort().join(",") !== "expected,operations" || typeof request.expected !== "string") fail4("expected\uC640 operations\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
24476
24796
|
return previewTransaction(cwd, request.expected, async (before) => {
|
|
24477
|
-
const result = editSpecPreview(before.
|
|
24797
|
+
const result = editSpecPreview(before.bundle, request.operations, generatePreviewId);
|
|
24478
24798
|
const writes = /* @__PURE__ */ new Map();
|
|
24479
24799
|
for (const spec2 of result.specs) {
|
|
24480
24800
|
const old = before.specs.find((s) => s.id === spec2.id);
|
|
24481
24801
|
if (!old || renderSpecPreview(old) !== renderSpecPreview(spec2)) writes.set(spec2.path, renderSpecPreview(spec2));
|
|
24482
24802
|
if (JSON.stringify(old?.design) !== JSON.stringify(spec2.design)) writes.set(spec2.path.replace(/requirements\.md$/, "design.md"), spec2.design ? renderDesignPreview(spec2.id, spec2.design) : null);
|
|
24483
24803
|
}
|
|
24804
|
+
const previousDocs = before.documents.flatMap((s) => s.documents);
|
|
24805
|
+
const nextDocs = result.documents.flatMap((s) => s.documents);
|
|
24806
|
+
for (const doc of nextDocs) {
|
|
24807
|
+
const old = previousDocs.find((d) => d.id === doc.id);
|
|
24808
|
+
if (old && old.path !== doc.path) writes.set(old.path, null);
|
|
24809
|
+
if (!old || renderDocument(old) !== renderDocument(doc) || old.path !== doc.path) writes.set(doc.path, renderDocument(doc));
|
|
24810
|
+
}
|
|
24811
|
+
for (const old of previousDocs) if (!nextDocs.some((d) => d.id === old.id)) writes.set(old.path, null);
|
|
24484
24812
|
return { writes, data: { results: result.results } };
|
|
24485
24813
|
}, publish);
|
|
24486
24814
|
}
|
|
@@ -24488,9 +24816,9 @@ var readLockedPreviewState = (root) => snapshot2(root);
|
|
|
24488
24816
|
async function previewTransaction(cwd, expected2, build, publish = rename, afterPublish) {
|
|
24489
24817
|
const { root, gitDir } = await specPreviewReader(cwd).location();
|
|
24490
24818
|
await failOnLegacyLock(gitDir);
|
|
24491
|
-
const
|
|
24819
|
+
const lock = join3(gitDir, "gitifact-spec-preview.lock");
|
|
24492
24820
|
try {
|
|
24493
|
-
await mkdir2(
|
|
24821
|
+
await mkdir2(lock);
|
|
24494
24822
|
} catch (e) {
|
|
24495
24823
|
if (e.code === "EEXIST") fail4("\uBA85\uC138 \uC800\uC7A5 \uC7A0\uAE08 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
24496
24824
|
throw e;
|
|
@@ -24515,7 +24843,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
|
|
|
24515
24843
|
if (before.stamp !== expected2) fail4("\uC77D\uC740 \uB4A4 \uBA85\uC138\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. working\uC73C\uB85C \uB2E4\uC2DC \uC77D\uC73C\uC138\uC694.");
|
|
24516
24844
|
const result = await build(before);
|
|
24517
24845
|
for (const [path3, after2] of result.writes) {
|
|
24518
|
-
if (
|
|
24846
|
+
if (!recordPathPattern.test(path3) || path3.startsWith(".tryce/") || path3.split("/").some((p) => p === ".." || p === "." || /[\\:\0]/.test(p))) fail4("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC800\uC7A5 \uACBD\uB85C\uC785\uB2C8\uB2E4.");
|
|
24519
24847
|
if (after2 !== null && Buffer.byteLength(after2) > 1024 * 1024) fail4("\uBA85\uC138 1 MiB \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
24520
24848
|
changed.push({ path: path3, before: before.files.get(path3) ?? null, after: after2 });
|
|
24521
24849
|
}
|
|
@@ -24525,7 +24853,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
|
|
|
24525
24853
|
else finalFiles.set(c.path, c.after);
|
|
24526
24854
|
}
|
|
24527
24855
|
if (finalFiles.size > 2e3 || [...finalFiles.values()].reduce((n, s) => n + Buffer.byteLength(s), 0) > 16 * 1024 * 1024) fail4("\uBA85\uC138 \uC804\uCCB4 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
24528
|
-
await writeFile(join3(
|
|
24856
|
+
await writeFile(join3(lock, "recovery.json"), JSON.stringify({ root, changed }), { flag: "wx" });
|
|
24529
24857
|
for (const c of changed) {
|
|
24530
24858
|
if (c.after === null) {
|
|
24531
24859
|
temporary.push("");
|
|
@@ -24551,7 +24879,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
|
|
|
24551
24879
|
if (after.stamp !== digest(JSON.stringify([...finalFiles].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (before.config ?? ""))) fail4("\uC800\uC7A5 \uC911 \uBA85\uC138 \uB610\uB294 \uC124\uC815\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
24552
24880
|
await result.recheck?.();
|
|
24553
24881
|
await afterPublish?.(after);
|
|
24554
|
-
return { ...result.data, stamp: after.stamp, paths: changed.map((c) => c.path), specs: after.specs, warnings: designReferenceWarnings(after.specs) };
|
|
24882
|
+
return { ...result.data, stamp: after.stamp, paths: changed.map((c) => c.path), specs: after.specs, documents: after.documents, warnings: designReferenceWarnings(after.specs) };
|
|
24555
24883
|
} catch (error62) {
|
|
24556
24884
|
if (error62 instanceof PreservedPreviewError) {
|
|
24557
24885
|
keepRecovery = true;
|
|
@@ -24576,7 +24904,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
|
|
|
24576
24904
|
keepRecovery = true;
|
|
24577
24905
|
}
|
|
24578
24906
|
}
|
|
24579
|
-
if (keepRecovery) fail4("\uC790\uB3D9 \uBCF5\uAD6C\uB97C \uC644\uB8CC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uB978 \uD3B8\uC9D1\uC744 \uBCF4\uC874\uD588\uC2B5\uB2C8\uB2E4. \uBCF5\uAD6C \uC790\uB8CC: " +
|
|
24907
|
+
if (keepRecovery) fail4("\uC790\uB3D9 \uBCF5\uAD6C\uB97C \uC644\uB8CC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uB978 \uD3B8\uC9D1\uC744 \uBCF4\uC874\uD588\uC2B5\uB2C8\uB2E4. \uBCF5\uAD6C \uC790\uB8CC: " + lock);
|
|
24580
24908
|
throw error62;
|
|
24581
24909
|
} finally {
|
|
24582
24910
|
for (const path3 of temporary.filter(Boolean)) await unlink2(path3).catch((e) => {
|
|
@@ -24585,10 +24913,10 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
|
|
|
24585
24913
|
for (const path3 of [...createdDirs].reverse()) await rmdir(path3).catch(() => {
|
|
24586
24914
|
});
|
|
24587
24915
|
if (!keepRecovery) {
|
|
24588
|
-
await unlink2(join3(
|
|
24916
|
+
await unlink2(join3(lock, "recovery.json")).catch((e) => {
|
|
24589
24917
|
if (e.code !== "ENOENT") throw e;
|
|
24590
24918
|
});
|
|
24591
|
-
await rmdir(
|
|
24919
|
+
await rmdir(lock);
|
|
24592
24920
|
}
|
|
24593
24921
|
}
|
|
24594
24922
|
}
|
|
@@ -24609,7 +24937,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
|
|
|
24609
24937
|
const snapshot3 = (oid2) => {
|
|
24610
24938
|
let value = snapshots.get(oid2);
|
|
24611
24939
|
if (!value) {
|
|
24612
|
-
value = reader.
|
|
24940
|
+
value = reader.readBundle(oid2).catch((e) => {
|
|
24613
24941
|
snapshots.delete(oid2);
|
|
24614
24942
|
throw e;
|
|
24615
24943
|
});
|
|
@@ -24627,7 +24955,8 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
|
|
|
24627
24955
|
if (cursor > 0 && expectedHead !== base.head) throw new SpecPreviewError("\uC774\uB825\uC774 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8\uB85C\uACE0\uCE68 \uD6C4 \uB2E4\uC2DC \uC870\uD68C\uD558\uC138\uC694.");
|
|
24628
24956
|
const current = await readWorkingPreviewState(root);
|
|
24629
24957
|
const bare = current.specs.map(({ history: _history, ...s }) => ({ ...s, contributors: [], updatedAt: null }));
|
|
24630
|
-
|
|
24958
|
+
const bareDocuments = current.documents.flatMap((set2) => set2.documents).map((d) => ({ ...d, updatedAt: null }));
|
|
24959
|
+
if (!base.head) return browserSpecsV1.parse({ contract: "browser-specs", version: 1, sessionId, head: null, observedAt: (/* @__PURE__ */ new Date()).toISOString(), working: bare.length > 0 || bareDocuments.length > 0, features: bare, documents: bareDocuments, events: [], contributors: [], contributorsLimited: false, nextCursor: null, boundary: false });
|
|
24631
24960
|
const head = base.head;
|
|
24632
24961
|
const features = await Promise.all(bare.map(async (feature2) => {
|
|
24633
24962
|
const folder = feature2.path.replace(/^\.(?:gitifact|tryce)\/(spec\/[^/]+)\/requirements\.md$/, "$1");
|
|
@@ -24642,9 +24971,22 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
|
|
|
24642
24971
|
}
|
|
24643
24972
|
return { ...feature2, contributors: [...people.values()].sort((a, b) => b.commits - a.commits), updatedAt: lines[0]?.split("\0")[2] ?? null };
|
|
24644
24973
|
}));
|
|
24974
|
+
const documents = await Promise.all(bareDocuments.map(async (doc) => ({ ...doc, updatedAt: (await git(["log", "--format=%aI", "--max-count=1", head, "--", doc.path])).trim() || null })));
|
|
24975
|
+
const documentDirs = Object.values(DOCUMENT_DIRS);
|
|
24645
24976
|
const [rows, dirty] = await Promise.all([
|
|
24646
|
-
git([
|
|
24647
|
-
|
|
24977
|
+
git([
|
|
24978
|
+
"log",
|
|
24979
|
+
"--first-parent",
|
|
24980
|
+
"--date-order",
|
|
24981
|
+
"--format=%H%x00%P%x00%aN%x00%aE%x00%aI%x00%cN%x00%s",
|
|
24982
|
+
"--max-count=11",
|
|
24983
|
+
"--skip=" + cursor,
|
|
24984
|
+
head,
|
|
24985
|
+
"--",
|
|
24986
|
+
...[".gitifact", ".tryce"].flatMap((d) => [`:(glob)${d}/spec/*/requirements.md`, `:(glob)${d}/spec/*/design.md`, `:(glob)${d}/spec/*/history.jsonl`]),
|
|
24987
|
+
...documentDirs.flatMap((d) => [`:(glob)${d}/**/*.md`, `:(glob)${d}/history.jsonl`])
|
|
24988
|
+
]),
|
|
24989
|
+
git(["status", "--porcelain=v1", "--", ".gitifact/spec", ...documentDirs])
|
|
24648
24990
|
]);
|
|
24649
24991
|
{
|
|
24650
24992
|
const people = /* @__PURE__ */ new Map();
|
|
@@ -24664,7 +25006,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
|
|
|
24664
25006
|
const [commit, parents, author, email3, date5, committer, message] = row.split("\0");
|
|
24665
25007
|
if (!commit || author === void 0 || email3 === void 0 || !date5 || committer === void 0 || message === void 0) throw new SpecPreviewError("Git \uC774\uB825 \uD615\uC2DD\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
|
|
24666
25008
|
const after = await snapshot3(commit);
|
|
24667
|
-
let before = [];
|
|
25009
|
+
let before = { specs: [], documents: [{ kind: "product", documents: [], history: [] }, { kind: "guide", documents: [], history: [] }] };
|
|
24668
25010
|
const parent = parents?.split(" ")[0];
|
|
24669
25011
|
if (parent) {
|
|
24670
25012
|
try {
|
|
@@ -24674,7 +25016,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
|
|
|
24674
25016
|
else throw error62;
|
|
24675
25017
|
}
|
|
24676
25018
|
}
|
|
24677
|
-
return
|
|
25019
|
+
return comparePreviewBundles(before, after).changes.map((c) => ({
|
|
24678
25020
|
key: commit + ":" + c.id,
|
|
24679
25021
|
commit,
|
|
24680
25022
|
author,
|
|
@@ -24699,6 +25041,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
|
|
|
24699
25041
|
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24700
25042
|
working: !!dirty.trim(),
|
|
24701
25043
|
features,
|
|
25044
|
+
documents,
|
|
24702
25045
|
events: groups.flat(),
|
|
24703
25046
|
contributors: peopleCache.data,
|
|
24704
25047
|
contributorsLimited: peopleCache.limited,
|
|
@@ -24780,6 +25123,21 @@ async function startBrowserServer(options) {
|
|
|
24780
25123
|
if (api) {
|
|
24781
25124
|
if (request.headers["sec-fetch-site"] === "cross-site" && !allowedOrigin) return fail8(response, 403, "FORBIDDEN", "\uC678\uBD80 \uD398\uC774\uC9C0\uC758 \uC694\uCCAD\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
24782
25125
|
if (url2.search && path3 !== "/api/v1/specs") return fail8(response, 400, "BAD_REQUEST", "\uC870\uD68C \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
25126
|
+
if (path3.startsWith("/api/v1/product/assets/")) {
|
|
25127
|
+
const name = path3.slice("/api/v1/product/assets/".length);
|
|
25128
|
+
if (request.method !== "GET") {
|
|
25129
|
+
response.setHeader("Allow", "GET");
|
|
25130
|
+
return fail8(response, 405, "METHOD_NOT_ALLOWED", "\uD5C8\uC6A9\uD558\uC9C0 \uC54A\uB294 \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4.");
|
|
25131
|
+
}
|
|
25132
|
+
if (!isProductAssetPath(".gitifact/product/" + name)) return fail8(response, 404, "NOT_FOUND", "\uC81C\uD488 \uC774\uBBF8\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
25133
|
+
const file2 = join4(initial.repository.rootPath, ".gitifact", "product", name);
|
|
25134
|
+
const stat3 = await lstat3(file2).catch(() => void 0);
|
|
25135
|
+
if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > 5 * 1024 * 1024) return fail8(response, 404, "NOT_FOUND", "\uC81C\uD488 \uC774\uBBF8\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
25136
|
+
const bytes = await readFile4(file2);
|
|
25137
|
+
response.writeHead(200, { "Content-Type": contentType(name.toLowerCase()), "Cache-Control": "no-cache", "Content-Length": bytes.length });
|
|
25138
|
+
response.end(bytes);
|
|
25139
|
+
return;
|
|
25140
|
+
}
|
|
24783
25141
|
const method = path3 === "/api/v1/status/refresh" ? "POST" : "GET";
|
|
24784
25142
|
if (!["/api/v1/session", "/api/v1/status", "/api/v1/status/refresh", "/api/v1/specs"].includes(path3)) return fail8(response, 404, "NOT_FOUND", "API\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
24785
25143
|
if (request.method !== method) {
|
|
@@ -24905,12 +25263,12 @@ async function runBrowser(options) {
|
|
|
24905
25263
|
|
|
24906
25264
|
// src/commands/spec-init.ts
|
|
24907
25265
|
import { readdir as readdir3 } from "node:fs/promises";
|
|
24908
|
-
import { join as
|
|
25266
|
+
import { join as join7 } from "node:path";
|
|
24909
25267
|
|
|
24910
25268
|
// src/adapters/git/init-repository.ts
|
|
24911
25269
|
import { createHash as createHash3 } from "node:crypto";
|
|
24912
|
-
import { readFile as
|
|
24913
|
-
import { join as
|
|
25270
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
25271
|
+
import { join as join5 } from "node:path";
|
|
24914
25272
|
function initRepository(cwd, inherited = process.env) {
|
|
24915
25273
|
const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
|
|
24916
25274
|
const runner = createGitRunner();
|
|
@@ -24930,10 +25288,10 @@ function initRepository(cwd, inherited = process.env) {
|
|
|
24930
25288
|
const gitDir = locations.length === 2 ? locations[0] : decodeGitLine(await git(["rev-parse", "--absolute-git-dir"], root));
|
|
24931
25289
|
const indexPath = locations.length === 2 ? locations[1] : decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], root));
|
|
24932
25290
|
for (const marker2 of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"]) {
|
|
24933
|
-
if (await fileInfo(
|
|
25291
|
+
if (await fileInfo(join5(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker2);
|
|
24934
25292
|
}
|
|
24935
25293
|
if (state.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uCDA9\uB3CC\uC744 \uBA3C\uC800 \uD574\uACB0\uD558\uC138\uC694.");
|
|
24936
|
-
const index = await
|
|
25294
|
+
const index = await readFile5(indexPath).catch((error62) => {
|
|
24937
25295
|
if (error62.code === "ENOENT") return Buffer.alloc(0);
|
|
24938
25296
|
throw error62;
|
|
24939
25297
|
});
|
|
@@ -24947,11 +25305,6 @@ function initRepository(cwd, inherited = process.env) {
|
|
|
24947
25305
|
stamp: JSON.stringify({ repository: state.repository, head: state.head, index: createHash3("sha256").update(index).digest("hex") })
|
|
24948
25306
|
};
|
|
24949
25307
|
},
|
|
24950
|
-
async trackedPaths(root, prefix, commit) {
|
|
24951
|
-
const indexed = await git(["ls-files", "-z", "--", prefix], root);
|
|
24952
|
-
const committed = commit ? await git(["ls-tree", "-r", "--name-only", "-z", commit, "--", prefix], root) : Buffer.alloc(0);
|
|
24953
|
-
return [...new Set(Buffer.concat([indexed, committed]).toString("utf8").split("\0").filter(Boolean))];
|
|
24954
|
-
},
|
|
24955
25308
|
async checkIgnore(root, path3 = ".gitifact/config.json") {
|
|
24956
25309
|
const output2 = (await git(["check-ignore", "--no-index", "-v", "-z", "--stdin"], root, [0, 1], Buffer.from(path3 + "\0"))).toString("utf8");
|
|
24957
25310
|
const fields2 = output2.split("\0");
|
|
@@ -24971,87 +25324,12 @@ function initRepository(cwd, inherited = process.env) {
|
|
|
24971
25324
|
};
|
|
24972
25325
|
}
|
|
24973
25326
|
|
|
24974
|
-
// src/
|
|
24975
|
-
|
|
24976
|
-
const repo = initRepository(cwd, env);
|
|
24977
|
-
const first = await repo.inspect();
|
|
24978
|
-
const root = first.state.repository.rootPath;
|
|
24979
|
-
const result = (config3, outcome) => ({
|
|
24980
|
-
contract: "project-init",
|
|
24981
|
-
version: 3,
|
|
24982
|
-
ok: true,
|
|
24983
|
-
outcome,
|
|
24984
|
-
rootPath: root,
|
|
24985
|
-
configPath: ".gitifact/config.json",
|
|
24986
|
-
schemaVersion: config3.schemaVersion,
|
|
24987
|
-
baseline: config3.baseline,
|
|
24988
|
-
integrations: { skills: "not-installed-by-init", hooks: "not-installed-by-init" }
|
|
24989
|
-
});
|
|
24990
|
-
const existing = async (text2) => {
|
|
24991
|
-
const config3 = parseManagedConfig(text2);
|
|
24992
|
-
if (!("schemaVersion" in config3)) throw new InitError("MIGRATION_REQUIRED", "\uAE30\uC874 \uD615\uC2DD\uC740 \uBCC4\uB3C4 \uC804\uD658\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC124\uC815\uACFC \uAE30\uB85D\uC744 \uBCF4\uC874\uD558\uC138\uC694.");
|
|
24993
|
-
await repo.validateBaseline(config3, root, first.state.head.commit, first.state.repository.objectFormat);
|
|
24994
|
-
if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text2) throw new InitError("INPUT_CHANGED", "\uCD08\uAE30\uD654 \uC870\uD68C \uC911 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
24995
|
-
return result(config3, "already-initialized");
|
|
24996
|
-
};
|
|
24997
|
-
const old = await readConfigFile(root);
|
|
24998
|
-
if (old !== void 0) return existing(old);
|
|
24999
|
-
if (first.trackedConfig) throw new InitError("CONFIG_DELETED", "\uAE30\uC874 \uC124\uC815\uC758 \uC0AD\uC81C\uB97C \uC2E0\uADDC \uB3C4\uC785\uC73C\uB85C \uCC98\uB9AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
25000
|
-
const checkRecords = async () => {
|
|
25001
|
-
if (await fileInfo(join5(root, "specs"))) throw new InitError("EXISTING_RECORDS", "\uAE30\uC874 specs \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
25002
|
-
const entries = await readdir3(join5(root, ".gitifact")).catch((e) => {
|
|
25003
|
-
if (e.code === "ENOENT") return [];
|
|
25004
|
-
throw e;
|
|
25005
|
-
});
|
|
25006
|
-
if (entries.some((name) => !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) throw new InitError("EXISTING_RECORDS", "\uC124\uC815 \uC5C6\uB294 .gitifact \uC790\uB8CC\uB97C \uC790\uB3D9 \uCC44\uD0DD\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
25007
|
-
};
|
|
25008
|
-
const config2 = { schemaVersion: 1, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
|
|
25009
|
-
const recheck = async () => {
|
|
25010
|
-
await checkRecords();
|
|
25011
|
-
await repo.checkIgnore(root);
|
|
25012
|
-
if ((await repo.inspect()).stamp !== first.stamp) throw new InitError("INPUT_CHANGED", "HEAD \uB610\uB294 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25013
|
-
if (await readConfigFile(root) !== void 0) throw new InitError("CONFIG_APPEARED", "\uB2E4\uB978 \uC2E4\uD589\uC774 \uC124\uC815\uC744 \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
25014
|
-
};
|
|
25015
|
-
await recheck();
|
|
25016
|
-
if (dryRun) return result(config2, "planned");
|
|
25017
|
-
try {
|
|
25018
|
-
await publishConfig(root, JSON.stringify(config2, null, 2) + "\n", recheck, beforePublish);
|
|
25019
|
-
} catch (error62) {
|
|
25020
|
-
if (error62.code === "EEXIST" || error62 instanceof InitError && error62.code === "CONFIG_APPEARED") {
|
|
25021
|
-
const concurrent = await readConfigFile(root);
|
|
25022
|
-
if (concurrent !== void 0) return existing(concurrent);
|
|
25023
|
-
}
|
|
25024
|
-
throw error62;
|
|
25025
|
-
}
|
|
25026
|
-
if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== JSON.stringify(config2, null, 2) + "\n") throw new InitError("INPUT_CHANGED_AFTER_WRITE", "\uC124\uC815 \uC0DD\uC131 \uD6C4 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
25027
|
-
return result(config2, "created");
|
|
25028
|
-
}
|
|
25029
|
-
|
|
25030
|
-
// src/commands/init.ts
|
|
25031
|
-
async function runInit(options) {
|
|
25032
|
-
try {
|
|
25033
|
-
const dto = await initializeSpecProject(process.cwd(), options.dryRun);
|
|
25034
|
-
process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
|
|
25035
|
-
\uC800\uC7A5 \uADDC\uC57D: schemaVersion 1
|
|
25036
|
-
\uC2A4\uD0AC\xB7\uD6C5\uC740 \uC124\uCE58\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
|
|
25037
|
-
` : JSON.stringify(dto) + "\n");
|
|
25038
|
-
} catch (error62) {
|
|
25039
|
-
const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
|
|
25040
|
-
const failure2 = { code: known ? error62.code : "INIT_FAILED", message: known ? error62.message : "\uCD08\uAE30\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uD30C\uC77C \uC811\uADFC \uAD8C\uD55C\uACFC \uC0DD\uC131\uB41C \uC124\uC815\xB7\uC784\uC2DC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694." };
|
|
25041
|
-
process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify({ contract: "project-init", version: 1, ok: false, error: failure2 }) + "\n");
|
|
25042
|
-
process.exitCode = 1;
|
|
25043
|
-
}
|
|
25044
|
-
}
|
|
25045
|
-
|
|
25046
|
-
// src/commands/skills.ts
|
|
25047
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
25048
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
25049
|
-
|
|
25050
|
-
// src/adapters/filesystem/skill-files.ts
|
|
25051
|
-
import { open as open3, readFile as readFile5, mkdir as mkdir3, realpath as realpath2, rename as rename2, link as link2, unlink as unlink3 } from "node:fs/promises";
|
|
25327
|
+
// src/adapters/filesystem/managed-file.ts
|
|
25328
|
+
import { open as open3, readFile as readFile6, mkdir as mkdir3, realpath as realpath2, rename as rename2, link as link2, unlink as unlink3 } from "node:fs/promises";
|
|
25052
25329
|
import { join as join6, dirname as dirname2 } from "node:path";
|
|
25053
25330
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
25054
|
-
|
|
25331
|
+
var DEFAULT_LIMIT = 1024 * 1024;
|
|
25332
|
+
async function managedPath(root, path3, create = false) {
|
|
25055
25333
|
let full = root;
|
|
25056
25334
|
const parts = path3.split("/");
|
|
25057
25335
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -25069,41 +25347,41 @@ async function skillPath(root, path3, create = false) {
|
|
|
25069
25347
|
}
|
|
25070
25348
|
return full;
|
|
25071
25349
|
}
|
|
25072
|
-
async function
|
|
25073
|
-
const full = await
|
|
25350
|
+
async function managedRead(root, path3, limit = DEFAULT_LIMIT) {
|
|
25351
|
+
const full = await managedPath(root, path3);
|
|
25074
25352
|
const before = await fileInfo(full);
|
|
25075
25353
|
if (!before) return null;
|
|
25076
|
-
if (before.size >
|
|
25077
|
-
const bytes = await
|
|
25078
|
-
await
|
|
25354
|
+
if (before.size > limit) throw new InitError("FILE_TOO_LARGE", limit / 1024 + " KiB\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4: " + path3);
|
|
25355
|
+
const bytes = await readFile6(full);
|
|
25356
|
+
await managedPath(root, path3);
|
|
25079
25357
|
const after = await fileInfo(full);
|
|
25080
|
-
if (!after || before.ino !== after.ino || before.dev !== after.dev || before.mtimeMs !== after.mtimeMs || bytes.length >
|
|
25358
|
+
if (!after || before.ino !== after.ino || before.dev !== after.dev || before.mtimeMs !== after.mtimeMs || bytes.length > limit) {
|
|
25081
25359
|
throw new InitError("INPUT_CHANGED", "\uC870\uD68C \uC911 \uD30C\uC77C\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25082
25360
|
}
|
|
25083
25361
|
try {
|
|
25084
25362
|
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
25085
25363
|
} catch {
|
|
25086
|
-
throw new InitError("
|
|
25364
|
+
throw new InitError("INVALID_UTF8", "UTF-8 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: " + path3);
|
|
25087
25365
|
}
|
|
25088
25366
|
}
|
|
25089
|
-
async function
|
|
25090
|
-
const full = await
|
|
25367
|
+
async function managedWrite(root, path3, previous, next, recheck) {
|
|
25368
|
+
const full = await managedPath(root, path3, true);
|
|
25091
25369
|
const directory = dirname2(full);
|
|
25092
25370
|
const owner = await fileInfo(directory);
|
|
25093
25371
|
const canonical = await realpath2(directory);
|
|
25094
25372
|
const unchanged = async () => {
|
|
25095
|
-
await
|
|
25373
|
+
await managedPath(root, path3);
|
|
25096
25374
|
const now = await fileInfo(directory);
|
|
25097
|
-
if (now?.ino !== owner?.ino || now?.dev !== owner?.dev || await realpath2(directory) !== canonical) throw new InitError("INPUT_CHANGED", "\
|
|
25375
|
+
if (now?.ino !== owner?.ino || now?.dev !== owner?.dev || await realpath2(directory) !== canonical) throw new InitError("INPUT_CHANGED", "\uB300\uC0C1 \uACBD\uB85C\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25098
25376
|
};
|
|
25099
25377
|
if (next === null) {
|
|
25100
25378
|
await recheck();
|
|
25101
25379
|
await unchanged();
|
|
25102
|
-
if (await
|
|
25380
|
+
if (await managedRead(root, path3) !== previous) throw new InitError("INPUT_CHANGED", "\uC81C\uAC70 \uB300\uC0C1\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25103
25381
|
if (previous !== null) await unlink3(full);
|
|
25104
25382
|
return;
|
|
25105
25383
|
}
|
|
25106
|
-
const temp = join6(directory, ".gitifact-
|
|
25384
|
+
const temp = join6(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
|
|
25107
25385
|
const handle = await open3(temp, "wx", 384);
|
|
25108
25386
|
const tempOwner = await handle.stat();
|
|
25109
25387
|
try {
|
|
@@ -25115,7 +25393,7 @@ async function skillWrite(root, path3, previous, next, recheck) {
|
|
|
25115
25393
|
}
|
|
25116
25394
|
await recheck();
|
|
25117
25395
|
await unchanged();
|
|
25118
|
-
if (await
|
|
25396
|
+
if (await managedRead(root, path3) !== previous || await readFile6(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", "\uAC8C\uC2DC \uB300\uC0C1\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25119
25397
|
if (previous === null) await link2(temp, full);
|
|
25120
25398
|
else await rename2(temp, full);
|
|
25121
25399
|
} finally {
|
|
@@ -25124,163 +25402,292 @@ async function skillWrite(root, path3, previous, next, recheck) {
|
|
|
25124
25402
|
if (now?.ino === tempOwner.ino && now.dev === tempOwner.dev) await unlink3(temp);
|
|
25125
25403
|
}
|
|
25126
25404
|
}
|
|
25127
|
-
|
|
25128
|
-
|
|
25129
|
-
|
|
25130
|
-
|
|
25131
|
-
|
|
25132
|
-
|
|
25133
|
-
|
|
25134
|
-
|
|
25135
|
-
|
|
25136
|
-
|
|
25137
|
-
|
|
25138
|
-
|
|
25139
|
-
|
|
25140
|
-
|
|
25405
|
+
|
|
25406
|
+
// src/commands/docs.ts
|
|
25407
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
25408
|
+
var docTopics = ["workflow", "spec", "design", "product", "commit"];
|
|
25409
|
+
var summaries = {
|
|
25410
|
+
workflow: "\uC2DC\uC791 \uC2DC \uD655\uC778\uD560 \uAC83, \uC694\uAD6C\uC0AC\uD56D\uC73C\uB85C \uB0A8\uAE38 \uC694\uCCAD\uC758 \uAD6C\uBD84, \uB9C8\uBB34\uB9AC \uBCF4\uACE0",
|
|
25411
|
+
spec: "\uC0AC\uC6A9\uC790 \uC2A4\uD1A0\uB9AC\xB7\uC218\uC6A9 \uC870\uAC74 \uD615\uC2DD, ID \uADDC\uCE59, spec save \uC785\uB825",
|
|
25412
|
+
design: "design.md \uBAA9\uCC28, \uCC38\uC870 \uC8FC\uC11D, \uAC1C\uC815 \uBC29\uC2DD",
|
|
25413
|
+
product: "PRODUCT.md\uC640 guides \uBB38\uC11C\uC758 \uC5ED\uD560\uACFC \uC800\uC7A5 \uBA85\uB839",
|
|
25414
|
+
commit: "spec commit \uC785\uB825, \uC774\uC720 \uAE30\uB85D, \uC2E4\uD328 \uD6C4 \uBCF5\uAD6C"
|
|
25415
|
+
};
|
|
25416
|
+
var isDocTopic = (value) => docTopics.includes(value);
|
|
25417
|
+
var readBundled = (topic) => readFile7(new URL("./docs/" + topic + ".md", import.meta.url), "utf8");
|
|
25418
|
+
function listDocTopics() {
|
|
25419
|
+
const width = Math.max(...docTopics.map((topic) => topic.length));
|
|
25420
|
+
return docTopics.map((topic) => topic.padEnd(width) + " " + summaries[topic]).join("\n") + "\n";
|
|
25421
|
+
}
|
|
25422
|
+
async function runDocs(topic, controls = {}) {
|
|
25423
|
+
if (topic === void 0) {
|
|
25424
|
+
process.stdout.write(listDocTopics());
|
|
25425
|
+
return;
|
|
25141
25426
|
}
|
|
25427
|
+
if (!isDocTopic(topic)) {
|
|
25428
|
+
const failure2 = { contract: "docs", version: 1, ok: false, error: { code: "UNKNOWN_TOPIC", message: "\uC54C \uC218 \uC5C6\uB294 \uC8FC\uC81C\uC785\uB2C8\uB2E4: " + topic + ". \uC0AC\uC6A9 \uAC00\uB2A5: " + docTopics.join(", ") } };
|
|
25429
|
+
process.stderr.write(JSON.stringify(failure2) + "\n");
|
|
25430
|
+
process.exitCode = 1;
|
|
25431
|
+
return;
|
|
25432
|
+
}
|
|
25433
|
+
process.stdout.write(await (controls.readDoc ?? readBundled)(topic));
|
|
25142
25434
|
}
|
|
25143
25435
|
|
|
25144
|
-
// src/commands/
|
|
25145
|
-
var
|
|
25146
|
-
var
|
|
25147
|
-
var
|
|
25148
|
-
var
|
|
25149
|
-
var
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
25155
|
-
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25159
|
-
|
|
25160
|
-
|
|
25436
|
+
// src/commands/agent-block.ts
|
|
25437
|
+
var AGENT_START = "<!-- GITIFACT:START -->";
|
|
25438
|
+
var AGENT_END = "<!-- GITIFACT:END -->";
|
|
25439
|
+
var candidatePaths = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", ".cursorrules", ".hermes.md", "HERMES.md"];
|
|
25440
|
+
var agentPresetNames = ["claude", "cursor", "codex", "hermes", "all"];
|
|
25441
|
+
var agentPresets = {
|
|
25442
|
+
claude: ["CLAUDE.md", ".claude/CLAUDE.md"],
|
|
25443
|
+
cursor: [".cursorrules", "AGENTS.md"],
|
|
25444
|
+
codex: ["AGENTS.md"],
|
|
25445
|
+
hermes: [".hermes.md", "HERMES.md", "AGENTS.md"]
|
|
25446
|
+
};
|
|
25447
|
+
function boilerplateFor(path3) {
|
|
25448
|
+
const name = path3 === "AGENTS.md" ? path3 : path3.split("/").pop().replace(/^\./, "").replace(/\.md$/, "");
|
|
25449
|
+
return "# " + name + "\n\nProject-specific guidance for AI coding agents.\n";
|
|
25450
|
+
}
|
|
25451
|
+
function renderAgentBlock(version2) {
|
|
25452
|
+
const lines = [
|
|
25453
|
+
AGENT_START,
|
|
25454
|
+
"gitifact v" + version2 + " \xB7 \uC800\uC7A5 \uADDC\uC57D schemaVersion 1",
|
|
25455
|
+
"CLI: \uBAA8\uB4E0 \uBA85\uB839\uC740 `gitifact <cmd>`\uB85C \uC2E4\uD589\uD55C\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uC9C0\uCE68\uC774 \uB2E4\uB978 \uC2E4\uD589 \uBC29\uBC95\uC744 \uC9C0\uC815\uD558\uBA74 \uADF8\uAC83\uC744 \uB530\uB978\uB2E4.",
|
|
25456
|
+
"",
|
|
25457
|
+
"\uC2DC\uC791\uD560 \uB54C:",
|
|
25458
|
+
"- `gitifact spec working`\uC73C\uB85C \uC81C\uD488 \uC124\uBA85\xB7\uC9C0\uCE68\xB7\uAE30\uB2A5 \uBA85\uC138\uB97C \uC77D\uACE0 git status\uC640 \uAE30\uC874 staging\uC744 \uD655\uC778\uD55C\uB2E4.",
|
|
25459
|
+
"- \uC774 \uBE14\uB85D\uC740 \uC694\uC57D\uC774\uB2E4. \uC0C1\uC138 \uD615\uC2DD\uC740 `gitifact docs <topic>`\uC73C\uB85C \uC77D\uACE0 \uAE30\uC5B5\uC73C\uB85C \uCC44\uC6B0\uC9C0 \uC54A\uB294\uB2E4.",
|
|
25460
|
+
"",
|
|
25461
|
+
"\uBB34\uC5C7\uC744 \uC694\uAD6C\uC0AC\uD56D\uC73C\uB85C \uB0A8\uAE30\uB294\uAC00 (\uC81C\uD488 \uB3D9\uC791\uACFC \uC720\uC9C0\uD560 \uC81C\uC57D\uB9CC):",
|
|
25462
|
+
"| \uC694\uCCAD | \uCC98\uB9AC |",
|
|
25463
|
+
"| --- | --- |",
|
|
25464
|
+
"| \uAC8C\uC2DC\uBB3C\uC744 \uC0AD\uC81C\uD560 \uC218 \uC788\uAC8C \uD574\uC8FC\uC138\uC694 | \uC694\uAD6C\uC0AC\uD56D\uC73C\uB85C \uC815\uB9AC\uD55C\uB2E4 |",
|
|
25465
|
+
"| \uC774 \uB0B4\uBD80 \uD568\uC218 \uC774\uB984\uC744 \uBC14\uAFD4\uC8FC\uC138\uC694 | \uC77C\uBC18 \uAD6C\uD604 \uBCC0\uACBD\uC774\uB2E4 |",
|
|
25466
|
+
"| \uC9C0\uAE08 \uD478\uC2DC\uD574\uC8FC\uC138\uC694 | \uC791\uC5C5 \uC9C0\uC2DC\uB2E4. \uB4F1\uB85D\uD558\uC9C0 \uC54A\uB294\uB2E4 |",
|
|
25467
|
+
"| \uC678\uBD80 \uC11C\uBE44\uC2A4 \uC5C6\uC774 \uB3D9\uC791\uD574\uC57C \uD569\uB2C8\uB2E4 | \uC81C\uD488 \uC81C\uC57D\uC73C\uB85C \uBA85\uC138\uC5D0 \uBC18\uC601\uD55C\uB2E4 |",
|
|
25468
|
+
"",
|
|
25469
|
+
"\uADDC\uCE59:",
|
|
25470
|
+
"- \uBA85\uC138\uB97C \uC800\uC7A5\uD558\uAE30 \uC804\uC5D0 `gitifact docs spec`\uC744 \uC77D\uB294\uB2E4. ID\uB294 CLI\uAC00 \uBC1C\uAE09\uD55C \uAC12\uB9CC \uC4F4\uB2E4.",
|
|
25471
|
+
"- \uC0C8 \uAE30\uB2A5\uC740 requirements.md\uC640 design.md\uB97C \uD568\uAED8 \uC815\uB9AC\uD55C\uB2E4(`gitifact docs design`). \uC694\uAD6C\uC0AC\uD56D\uB9CC \uC694\uCCAD\uBC1B\uC73C\uBA74 \uB530\uB978\uB2E4.",
|
|
25472
|
+
"- \uCEE4\uBC0B \uC694\uCCAD\uC744 \uBC1B\uC73C\uBA74 `gitifact docs commit`\uC744 \uC77D\uACE0 \uBA85\uC138\xB7\uC774\uC720\xB7\uCF54\uB4DC\xB7\uD14C\uC2A4\uD2B8\uB97C \uD568\uAED8 \uCEE4\uBC0B\uD55C\uB2E4.",
|
|
25473
|
+
"- \uC790\uB3D9 \uAE30\uB85D\uC740 \uCEE4\uBC0B \uAD8C\uD55C\uC774 \uC544\uB2C8\uB2E4. \uC0AC\uC6A9\uC790 \uC694\uCCAD\uC774\uB098 \uBA85\uC2DC\uC801 \uD504\uB85C\uC81D\uD2B8 \uC815\uCC45\uC774 \uC788\uC744 \uB54C\uB9CC \uCEE4\uBC0B\uD558\uACE0 \uD478\uC2DC\uB294 \uBCC4\uB3C4 \uC694\uCCAD\uC744 \uB530\uB978\uB2E4.",
|
|
25474
|
+
"- \uBD88\uBA85\uD655\uD55C \uC81C\uD488 \uB3D9\uC791\uB9CC \uC9C8\uBB38\uD558\uACE0 \uB3C5\uB9BD\uC801\uC778 \uC791\uC5C5\uC740 \uC9C4\uD589\uD55C\uB2E4. \uAE30\uC874 \uAE30\uB2A5 \uC804\uCCB4 \uB3C4\uCD9C\uC740 \uC694\uCCAD\uBC1B\uC558\uC744 \uB54C \uD55C\uB2E4.",
|
|
25475
|
+
"- SELF-CHECK: save\xB7commit \uC785\uB825\uC744 \uB9CC\uB4E4\uAE30 \uC804\uC5D0 \uD574\uB2F9 docs\uB97C \uB2E4\uC2DC \uC77D\uACE0 \uD615\uC2DD\uC744 \uB300\uC870\uD55C\uB2E4. \uD655\uC2E4\uD558\uC9C0 \uC54A\uC73C\uBA74 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 `gitifact docs <topic>`\uC744 \uC2E4\uD589\uD55C\uB2E4.",
|
|
25476
|
+
"",
|
|
25477
|
+
"MORE CLI:",
|
|
25478
|
+
" docs <topic> " + docTopics.join(", "),
|
|
25479
|
+
" spec working \uD604\uC7AC \uBA85\uC138\xB7\uC81C\uD488\xB7\uC9C0\uCE68 \uC804\uCCB4\uC640 stamp",
|
|
25480
|
+
" spec save --file <json>: \uC694\uAD6C\uC0AC\uD56D\xB7\uC124\uACC4\xB7\uBB38\uC11C \uC800\uC7A5",
|
|
25481
|
+
" spec commit --file <json>: \uBCC0\uACBD \uC774\uC720 \uAE30\uB85D\uACFC \uCEE4\uBC0B\uC744 \uD55C \uBC88\uC5D0",
|
|
25482
|
+
" browser \uC77D\uAE30 \uC804\uC6A9 \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30",
|
|
25483
|
+
" init \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uC774 \uBE14\uB85D\uC744 \uAC31\uC2E0\uD55C\uB2E4",
|
|
25484
|
+
AGENT_END
|
|
25485
|
+
];
|
|
25486
|
+
return lines.join("\n");
|
|
25487
|
+
}
|
|
25488
|
+
function findBlock(text2) {
|
|
25489
|
+
const start = text2.indexOf(AGENT_START);
|
|
25490
|
+
if (start === -1) {
|
|
25491
|
+
if (text2.includes(AGENT_END)) throw new InitError("AGENT_DOCS_MALFORMED", "\uC2DC\uC791 \uB9C8\uCEE4 \uC5C6\uC774 \uC885\uB8CC \uB9C8\uCEE4\uB9CC \uC788\uC2B5\uB2C8\uB2E4. \uBE14\uB85D\uC744 \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
|
|
25492
|
+
return null;
|
|
25161
25493
|
}
|
|
25162
|
-
|
|
25494
|
+
const end = text2.indexOf(AGENT_END, start + AGENT_START.length);
|
|
25495
|
+
if (end === -1) throw new InitError("AGENT_DOCS_MALFORMED", "\uC885\uB8CC \uB9C8\uCEE4\uAC00 \uC5C6\uB294 GITIFACT \uBE14\uB85D\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uBE14\uB85D\uC744 \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
|
|
25496
|
+
if (text2.indexOf(AGENT_START, start + AGENT_START.length) !== -1) throw new InitError("AGENT_DOCS_MALFORMED", "GITIFACT \uC2DC\uC791 \uB9C8\uCEE4\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4. \uC911\uBCF5 \uBE14\uB85D\uC744 \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
|
|
25497
|
+
return { start, end: end + AGENT_END.length };
|
|
25498
|
+
}
|
|
25499
|
+
function isWrapperFile(text2) {
|
|
25500
|
+
const found = findBlock(text2);
|
|
25501
|
+
const own2 = found ? text2.slice(0, found.start) + text2.slice(found.end) : text2;
|
|
25502
|
+
const lines = own2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
25503
|
+
return lines.length > 0 && lines.every((line) => /^@(?:\.\/)?[^\s]+$/.test(line) && candidatePaths.includes(line.replace(/^@(?:\.\/)?/, "")));
|
|
25504
|
+
}
|
|
25505
|
+
function resolveAgentPaths(preset, existing) {
|
|
25506
|
+
if (preset && preset !== "all") {
|
|
25507
|
+
const order = agentPresets[preset];
|
|
25508
|
+
const found = order.find((path3) => existing.has(path3));
|
|
25509
|
+
return found ? { inject: [found], create: null } : { inject: [], create: order[order.length - 1] };
|
|
25510
|
+
}
|
|
25511
|
+
const inject = candidatePaths.filter((path3) => existing.has(path3) && !isWrapperFile(existing.get(path3)));
|
|
25512
|
+
if (inject.length > 0) return { inject, create: null };
|
|
25513
|
+
return { inject: [], create: "AGENTS.md" };
|
|
25514
|
+
}
|
|
25515
|
+
var eolOf = (text2) => text2?.includes("\r\n") ? "\r\n" : "\n";
|
|
25516
|
+
function injectBlock(text2, block, boilerplate) {
|
|
25517
|
+
const eol = eolOf(text2);
|
|
25518
|
+
const body = block.split("\n").join(eol);
|
|
25519
|
+
if (text2 === null) return boilerplate.split("\n").join(eol) + eol + body + eol;
|
|
25520
|
+
const found = findBlock(text2);
|
|
25521
|
+
if (found) return text2.slice(0, found.start) + body + text2.slice(found.end);
|
|
25522
|
+
const head = text2.replace(/\s+$/, "");
|
|
25523
|
+
return (head ? head + eol + eol : "") + body + eol;
|
|
25524
|
+
}
|
|
25525
|
+
function removeBlock(text2, boilerplate) {
|
|
25526
|
+
const found = findBlock(text2);
|
|
25527
|
+
if (!found) return text2;
|
|
25528
|
+
const eol = eolOf(text2);
|
|
25529
|
+
const before = text2.slice(0, found.start).replace(/\s+$/, "");
|
|
25530
|
+
const after = text2.slice(found.end).replace(/^\s+/, "");
|
|
25531
|
+
const rest = before && after ? before + eol + eol + after : before ? before + eol : after;
|
|
25532
|
+
const normalize = (value) => value.replace(/\r\n/g, "\n").trim();
|
|
25533
|
+
if (!normalize(rest) || normalize(rest) === normalize(boilerplate)) return null;
|
|
25534
|
+
return rest;
|
|
25535
|
+
}
|
|
25536
|
+
|
|
25537
|
+
// src/commands/agent-docs.ts
|
|
25538
|
+
var skippedAgentDocs = { mode: "skip", paths: [], writes: [] };
|
|
25539
|
+
async function planAgentDocs(root, options) {
|
|
25540
|
+
if (options.skip) return skippedAgentDocs;
|
|
25541
|
+
const existing = /* @__PURE__ */ new Map();
|
|
25542
|
+
for (const path3 of candidatePaths) {
|
|
25543
|
+
const text2 = await managedRead(root, path3);
|
|
25544
|
+
if (text2 !== null) existing.set(path3, text2);
|
|
25545
|
+
}
|
|
25546
|
+
const writes = [];
|
|
25547
|
+
const paths2 = [];
|
|
25548
|
+
if (options.remove) {
|
|
25549
|
+
const scope = options.agent && options.agent !== "all" ? agentPresets[options.agent] : candidatePaths;
|
|
25550
|
+
for (const path3 of scope) {
|
|
25551
|
+
const text2 = existing.get(path3);
|
|
25552
|
+
if (text2 === void 0 || !findBlock(text2)) continue;
|
|
25553
|
+
paths2.push(path3);
|
|
25554
|
+
writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
|
|
25555
|
+
}
|
|
25556
|
+
return { mode: "remove", paths: paths2, writes };
|
|
25557
|
+
}
|
|
25558
|
+
const block = renderAgentBlock(options.version);
|
|
25559
|
+
const { inject, create } = resolveAgentPaths(options.agent, existing);
|
|
25560
|
+
for (const path3 of inject) {
|
|
25561
|
+
const previous = existing.get(path3);
|
|
25562
|
+
const next = injectBlock(previous, block, boilerplateFor(path3));
|
|
25563
|
+
paths2.push(path3);
|
|
25564
|
+
if (next !== previous) writes.push({ path: path3, previous, next });
|
|
25565
|
+
}
|
|
25566
|
+
if (create) {
|
|
25567
|
+
paths2.push(create);
|
|
25568
|
+
writes.push({ path: create, previous: null, next: injectBlock(null, block, boilerplateFor(create)) });
|
|
25569
|
+
}
|
|
25570
|
+
for (const [path3, text2] of existing) {
|
|
25571
|
+
if (paths2.includes(path3) || !isWrapperFile(text2) || !findBlock(text2)) continue;
|
|
25572
|
+
writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
|
|
25573
|
+
}
|
|
25574
|
+
return { mode: "install", paths: paths2, writes };
|
|
25575
|
+
}
|
|
25576
|
+
async function applyAgentDocs(root, plan, recheck) {
|
|
25577
|
+
for (const write of plan.writes) await managedWrite(root, write.path, write.previous, write.next, recheck);
|
|
25578
|
+
return plan.writes.map((write) => write.path);
|
|
25163
25579
|
}
|
|
25164
|
-
|
|
25580
|
+
|
|
25581
|
+
// src/commands/spec-init.ts
|
|
25582
|
+
async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish, agentDocs) {
|
|
25165
25583
|
const repo = initRepository(cwd, env);
|
|
25166
25584
|
const first = await repo.inspect();
|
|
25167
25585
|
const root = first.state.repository.rootPath;
|
|
25168
|
-
const
|
|
25169
|
-
|
|
25170
|
-
|
|
25171
|
-
|
|
25172
|
-
|
|
25173
|
-
|
|
25174
|
-
|
|
25175
|
-
|
|
25176
|
-
|
|
25177
|
-
|
|
25178
|
-
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
25182
|
-
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
|
|
25186
|
-
|
|
25187
|
-
|
|
25188
|
-
if (
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
const
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
version: 1,
|
|
25200
|
-
ok: true,
|
|
25201
|
-
outcome,
|
|
25202
|
-
rootPath: root,
|
|
25203
|
-
agent: agent ?? null,
|
|
25204
|
-
source,
|
|
25205
|
-
target: agent === "claude" ? target : null,
|
|
25206
|
-
sourcePreserved: original !== null,
|
|
25207
|
-
sessionState: "not-observed"
|
|
25586
|
+
const docsPlan = agentDocs ? await planAgentDocs(root, agentDocs) : skippedAgentDocs;
|
|
25587
|
+
const result = (config3, outcome) => projectInitV4.parse({
|
|
25588
|
+
contract: "project-init",
|
|
25589
|
+
version: 4,
|
|
25590
|
+
ok: true,
|
|
25591
|
+
outcome,
|
|
25592
|
+
rootPath: root,
|
|
25593
|
+
configPath: ".gitifact/config.json",
|
|
25594
|
+
schemaVersion: config3.schemaVersion,
|
|
25595
|
+
baseline: config3.baseline,
|
|
25596
|
+
agentDocs: { mode: docsPlan.mode, paths: docsPlan.paths }
|
|
25597
|
+
});
|
|
25598
|
+
const existing = async (text3) => {
|
|
25599
|
+
const config3 = parseManagedConfig(text3);
|
|
25600
|
+
if (!("schemaVersion" in config3)) throw new InitError("MIGRATION_REQUIRED", "\uAE30\uC874 \uD615\uC2DD\uC740 \uBCC4\uB3C4 \uC804\uD658\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC124\uC815\uACFC \uAE30\uB85D\uC744 \uBCF4\uC874\uD558\uC138\uC694.");
|
|
25601
|
+
await repo.validateBaseline(config3, root, first.state.head.commit, first.state.repository.objectFormat);
|
|
25602
|
+
const unchanged = async () => {
|
|
25603
|
+
if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text3) throw new InitError("INPUT_CHANGED", "\uCD08\uAE30\uD654 \uC870\uD68C \uC911 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25604
|
+
};
|
|
25605
|
+
await unchanged();
|
|
25606
|
+
if (!dryRun) await applyAgentDocs(root, docsPlan, unchanged);
|
|
25607
|
+
return result(config3, "already-initialized");
|
|
25608
|
+
};
|
|
25609
|
+
const old = await readConfigFile(root);
|
|
25610
|
+
if (old !== void 0) return existing(old);
|
|
25611
|
+
if (first.trackedConfig) throw new InitError("CONFIG_DELETED", "\uAE30\uC874 \uC124\uC815\uC758 \uC0AD\uC81C\uB97C \uC2E0\uADDC \uB3C4\uC785\uC73C\uB85C \uCC98\uB9AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
25612
|
+
const checkRecords = async () => {
|
|
25613
|
+
if (await fileInfo(join7(root, "specs"))) throw new InitError("EXISTING_RECORDS", "\uAE30\uC874 specs \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
25614
|
+
const entries = await readdir3(join7(root, ".gitifact")).catch((e) => {
|
|
25615
|
+
if (e.code === "ENOENT") return [];
|
|
25616
|
+
throw e;
|
|
25208
25617
|
});
|
|
25209
|
-
if (
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
if (!ignored) throw new InitError("LOCAL_SKILL_NOT_IGNORED", "\uB85C\uCEEC \uACBD\uB85C\uAC00 ignore\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25618
|
+
if (entries.some((name) => !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) throw new InitError("EXISTING_RECORDS", "\uC124\uC815 \uC5C6\uB294 .gitifact \uC790\uB8CC\uB97C \uC790\uB3D9 \uCC44\uD0DD\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
25619
|
+
};
|
|
25620
|
+
const config2 = { schemaVersion: 1, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
|
|
25621
|
+
const text2 = JSON.stringify(config2, null, 2) + "\n";
|
|
25622
|
+
const recheck = async () => {
|
|
25623
|
+
await checkRecords();
|
|
25624
|
+
await repo.checkIgnore(root);
|
|
25625
|
+
if ((await repo.inspect()).stamp !== first.stamp) throw new InitError("INPUT_CHANGED", "HEAD \uB610\uB294 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25626
|
+
if (await readConfigFile(root) !== void 0) throw new InitError("CONFIG_APPEARED", "\uB2E4\uB978 \uC2E4\uD589\uC774 \uC124\uC815\uC744 \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
25627
|
+
};
|
|
25628
|
+
await recheck();
|
|
25629
|
+
if (dryRun) return result(config2, "planned");
|
|
25630
|
+
try {
|
|
25631
|
+
await publishConfig(root, text2, recheck, beforePublish);
|
|
25632
|
+
} catch (error62) {
|
|
25633
|
+
if (error62.code === "EEXIST" || error62 instanceof InitError && error62.code === "CONFIG_APPEARED") {
|
|
25634
|
+
const concurrent = await readConfigFile(root);
|
|
25635
|
+
if (concurrent !== void 0) return existing(concurrent);
|
|
25228
25636
|
}
|
|
25229
|
-
|
|
25230
|
-
|
|
25231
|
-
|
|
25232
|
-
|
|
25233
|
-
if (await skillRead(root, source) !== original || await skillRead(root, target) !== copy) throw new InitError("INPUT_CHANGED", "\uC2A4\uD0AC \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25234
|
-
};
|
|
25235
|
-
await skillWrite(root, manifest, saved, journal, inputs);
|
|
25236
|
-
await controls.afterJournal?.();
|
|
25237
|
-
if (original === null && wanted !== null) await skillWrite(root, source, null, wanted, recheck);
|
|
25238
|
-
if (copy !== nextCopy) await skillWrite(root, target, copy, nextCopy, recheck);
|
|
25239
|
-
await skillWrite(root, manifest, journal, stateText([digest2(nextCopy)]), recheck);
|
|
25240
|
-
await recheck();
|
|
25241
|
-
if (await skillRead(root, source) !== wanted || await skillRead(root, target) !== nextCopy) throw new InitError("INPUT_CHANGED", "\uAC8C\uC2DC \uD6C4 \uC2A4\uD0AC\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC870\uD68C\uD558\uC138\uC694.");
|
|
25242
|
-
return result(action === "remove" ? "removed" : action === "install" ? "installed" : "synced");
|
|
25637
|
+
throw error62;
|
|
25638
|
+
}
|
|
25639
|
+
const published = async () => {
|
|
25640
|
+
if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text2) throw new InitError("INPUT_CHANGED_AFTER_WRITE", "\uC124\uC815 \uC0DD\uC131 \uD6C4 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
25243
25641
|
};
|
|
25244
|
-
|
|
25642
|
+
await published();
|
|
25643
|
+
await applyAgentDocs(root, docsPlan, published);
|
|
25644
|
+
return result(config2, "created");
|
|
25245
25645
|
}
|
|
25246
|
-
|
|
25646
|
+
|
|
25647
|
+
// src/commands/init.ts
|
|
25648
|
+
async function runInit(options, version2) {
|
|
25247
25649
|
try {
|
|
25248
|
-
const
|
|
25249
|
-
|
|
25250
|
-
|
|
25251
|
-
|
|
25252
|
-
|
|
25650
|
+
const dto = await initializeSpecProject(
|
|
25651
|
+
process.cwd(),
|
|
25652
|
+
options.dryRun,
|
|
25653
|
+
process.env,
|
|
25654
|
+
void 0,
|
|
25655
|
+
{ version: version2, agent: options.agent, remove: options.removeAgents, skip: options.skipAgents }
|
|
25656
|
+
);
|
|
25657
|
+
if (!dto.ok) throw new Error("unreachable");
|
|
25658
|
+
const docs = dto.agentDocs.mode === "skip" ? "\uAC74\uB108\uB700" : dto.agentDocs.paths.join(", ") + (dto.agentDocs.mode === "remove" ? " (\uBE14\uB85D \uC81C\uAC70)" : dto.outcome === "planned" ? " (\uBE14\uB85D \uC124\uCE58 \uC608\uC815)" : " (\uBE14\uB85D \uAC31\uC2E0)");
|
|
25659
|
+
process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
|
|
25660
|
+
\uC800\uC7A5 \uADDC\uC57D: schemaVersion 1
|
|
25661
|
+
\uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68: ${docs}
|
|
25662
|
+
` : JSON.stringify(dto) + "\n");
|
|
25253
25663
|
} catch (error62) {
|
|
25254
|
-
const
|
|
25255
|
-
|
|
25256
|
-
|
|
25257
|
-
} });
|
|
25258
|
-
console.error(JSON.stringify(failure2));
|
|
25664
|
+
const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
|
|
25665
|
+
const failure2 = { code: known ? error62.code : "INIT_FAILED", message: known ? error62.message : "\uCD08\uAE30\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uD30C\uC77C \uC811\uADFC \uAD8C\uD55C\uACFC \uC0DD\uC131\uB41C \uC124\uC815\xB7\uC784\uC2DC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694." };
|
|
25666
|
+
process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(projectInitV4.parse({ contract: "project-init", version: 4, ok: false, error: failure2 })) + "\n");
|
|
25259
25667
|
process.exitCode = 1;
|
|
25260
25668
|
}
|
|
25261
25669
|
}
|
|
25262
25670
|
|
|
25263
25671
|
// src/commands/spec-preview.ts
|
|
25264
|
-
import { join as
|
|
25265
|
-
import { readFile as
|
|
25672
|
+
import { join as join12 } from "node:path";
|
|
25673
|
+
import { readFile as readFile11, stat as stat2 } from "node:fs/promises";
|
|
25266
25674
|
|
|
25267
25675
|
// src/adapters/filesystem/spec-preview-prepare.ts
|
|
25268
|
-
import { lstat as
|
|
25269
|
-
import { join as
|
|
25270
|
-
import { createHash as
|
|
25676
|
+
import { lstat as lstat4 } from "node:fs/promises";
|
|
25677
|
+
import { join as join8 } from "node:path";
|
|
25678
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
25271
25679
|
var fail5 = (message) => {
|
|
25272
25680
|
throw new SpecPreviewError(message);
|
|
25273
25681
|
};
|
|
25274
25682
|
var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
25275
|
-
var expected = (base, stamp) =>
|
|
25683
|
+
var expected = (base, stamp) => createHash4("sha256").update(JSON.stringify({ base, stamp })).digest("hex");
|
|
25276
25684
|
var previewExpected = expected;
|
|
25277
|
-
var parseFiles = parsePreviewFiles;
|
|
25278
25685
|
async function readPreviewContext(cwd) {
|
|
25279
25686
|
const reader = specPreviewReader(cwd);
|
|
25280
25687
|
const { gitDir } = await reader.location();
|
|
25281
25688
|
const checkOperation = async () => {
|
|
25282
25689
|
for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) {
|
|
25283
|
-
const exists = await
|
|
25690
|
+
const exists = await lstat4(join8(gitDir, name)).catch((e) => {
|
|
25284
25691
|
if (e.code === "ENOENT") return void 0;
|
|
25285
25692
|
throw e;
|
|
25286
25693
|
});
|
|
@@ -25291,20 +25698,21 @@ async function readPreviewContext(cwd) {
|
|
|
25291
25698
|
const base = await reader.baseline();
|
|
25292
25699
|
const working = await readWorkingPreviewState(cwd);
|
|
25293
25700
|
const files = base.head ? await reader.files(base.head, working.config !== void 0) : /* @__PURE__ */ new Map();
|
|
25294
|
-
const
|
|
25701
|
+
const bundle = parsePreviewBundle(files);
|
|
25702
|
+
const specs = bundle.specs;
|
|
25295
25703
|
const recheck = async () => {
|
|
25296
25704
|
await checkOperation();
|
|
25297
25705
|
if (!same(base, await reader.baseline())) fail5("HEAD \uB610\uB294 \uBE0C\uB79C\uCE58\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. changes\uBD80\uD130 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.");
|
|
25298
25706
|
};
|
|
25299
|
-
return { reader, base, files, specs, recheck, working };
|
|
25707
|
+
return { reader, base, files, specs, bundle, recheck, working };
|
|
25300
25708
|
}
|
|
25301
25709
|
var context = readPreviewContext;
|
|
25302
25710
|
async function readFinalPreviewChanges(cwd) {
|
|
25303
25711
|
const c = await context(cwd);
|
|
25304
25712
|
const current = await readWorkingPreviewState(cwd);
|
|
25305
|
-
const delta = finalSpecPreviewChanges(c.
|
|
25713
|
+
const delta = finalSpecPreviewChanges(c.bundle, current.bundle);
|
|
25306
25714
|
await c.recheck();
|
|
25307
|
-
const pendingReasons =
|
|
25715
|
+
const pendingReasons = pendingPreviewReasons(c.bundle, current.bundle);
|
|
25308
25716
|
return { base: c.base, expected: expected(c.base, current.stamp), stamp: current.stamp, pendingReasons, ...delta };
|
|
25309
25717
|
}
|
|
25310
25718
|
async function prepareWorkingPreview(cwd, input2, publish) {
|
|
@@ -25316,7 +25724,7 @@ async function prepareWorkingPreview(cwd, input2, publish) {
|
|
|
25316
25724
|
if (request.expected !== expected(c.base, current.stamp)) fail5("\uC900\uBE44 \uC785\uB825\uC774 \uC624\uB798\uB410\uC2B5\uB2C8\uB2E4. changes\uBD80\uD130 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.");
|
|
25317
25725
|
const result = await previewTransaction(cwd, current.stamp, async (before) => {
|
|
25318
25726
|
await c.recheck();
|
|
25319
|
-
const prepared = prepareSpecPreview(c.
|
|
25727
|
+
const prepared = prepareSpecPreview(c.bundle, before.bundle, c.files, before.files, request.reasons, () => generatePreviewId("H"));
|
|
25320
25728
|
return { writes: prepared.writes, data: { changes: prepared.changes, specChanges: prepared.specChanges, reasons: prepared.reasons, withoutReason: prepared.withoutReason }, recheck: c.recheck };
|
|
25321
25729
|
}, publish);
|
|
25322
25730
|
return { ...result, base: c.base, expected: expected(c.base, result.stamp), verification: { base: c.base, stamp: result.stamp } };
|
|
@@ -25328,15 +25736,15 @@ async function verifyPreparedPreview(cwd, input2, staged) {
|
|
|
25328
25736
|
const c = await context(cwd);
|
|
25329
25737
|
const current = await readWorkingPreviewState(cwd);
|
|
25330
25738
|
if (!same(request.base, c.base) || request.stamp !== current.stamp) fail5("\uC900\uBE44 \uB4A4 HEAD \uB610\uB294 \uBA85\uC138\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC900\uBE44\uD558\uC138\uC694.");
|
|
25331
|
-
finalSpecPreviewChanges(c.
|
|
25332
|
-
|
|
25739
|
+
finalSpecPreviewChanges(c.bundle, current.bundle);
|
|
25740
|
+
comparePreviewBundles(c.bundle, current.bundle);
|
|
25333
25741
|
if (staged) {
|
|
25334
25742
|
const index = await c.reader.index();
|
|
25335
25743
|
if (index.size !== current.files.size) fail5("staging\uC758 \uBA85\uC138 \uBC94\uC704\uAC00 \uC900\uBE44\uD55C \uC6D0\uBB38\uACFC \uB2E4\uB985\uB2C8\uB2E4.");
|
|
25336
|
-
for (const [path3,
|
|
25744
|
+
for (const [path3, source] of current.files) {
|
|
25337
25745
|
const oid2 = index.get(path3);
|
|
25338
|
-
const bytes = Buffer.from(
|
|
25339
|
-
const hash3 =
|
|
25746
|
+
const bytes = Buffer.from(source);
|
|
25747
|
+
const hash3 = createHash4(oid2?.length === 64 ? "sha256" : "sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
|
|
25340
25748
|
if (oid2 !== hash3) fail5("staging \uC6D0\uBB38\uC774 \uC900\uBE44\uD55C \uD30C\uC77C\uACFC \uB2E4\uB985\uB2C8\uB2E4: " + path3);
|
|
25341
25749
|
}
|
|
25342
25750
|
if (!same([...index], [...await c.reader.index()])) fail5("\uD655\uC778 \uC911 staging\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
@@ -25348,22 +25756,22 @@ async function verifyPreparedPreview(cwd, input2, staged) {
|
|
|
25348
25756
|
|
|
25349
25757
|
// src/commands/spec-preview-commit.ts
|
|
25350
25758
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
25351
|
-
import { readFile as
|
|
25352
|
-
import { join as
|
|
25759
|
+
import { readFile as readFile9, mkdir as mkdir4, open as open4, rename as rename3, unlink as unlink4, rmdir as rmdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
25760
|
+
import { join as join10 } from "node:path";
|
|
25353
25761
|
|
|
25354
25762
|
// src/commands/spec-commit-files.ts
|
|
25355
|
-
import { createHash as
|
|
25356
|
-
import { lstat as
|
|
25357
|
-
import { join as
|
|
25763
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
25764
|
+
import { lstat as lstat5, readFile as readFile8 } from "node:fs/promises";
|
|
25765
|
+
import { join as join9 } from "node:path";
|
|
25358
25766
|
var fail6 = (message) => {
|
|
25359
25767
|
throw new SpecPreviewError(message);
|
|
25360
25768
|
};
|
|
25361
|
-
var hash2 = (value) =>
|
|
25362
|
-
var optional2 = (path3) =>
|
|
25769
|
+
var hash2 = (value) => createHash5("sha256").update(value).digest("hex");
|
|
25770
|
+
var optional2 = (path3) => readFile8(path3).catch((e) => {
|
|
25363
25771
|
if (e.code === "ENOENT") return null;
|
|
25364
25772
|
throw e;
|
|
25365
25773
|
});
|
|
25366
|
-
var info2 = (path3) =>
|
|
25774
|
+
var info2 = (path3) => lstat5(path3).catch((e) => {
|
|
25367
25775
|
if (e.code === "ENOENT") return void 0;
|
|
25368
25776
|
throw e;
|
|
25369
25777
|
});
|
|
@@ -25375,14 +25783,14 @@ async function fingerprint(root, path3) {
|
|
|
25375
25783
|
validPath2(path3);
|
|
25376
25784
|
const parts = path3.split("/");
|
|
25377
25785
|
for (let i = 1; i <= parts.length; i++) {
|
|
25378
|
-
const
|
|
25379
|
-
const stat3 = await info2(
|
|
25786
|
+
const target = join9(root, ...parts.slice(0, i));
|
|
25787
|
+
const stat3 = await info2(target);
|
|
25380
25788
|
if (!stat3) return null;
|
|
25381
25789
|
if (stat3.isSymbolicLink() || (i < parts.length ? !stat3.isDirectory() : !stat3.isFile())) fail6("\uB9C1\uD06C\xB7\uB514\uB809\uD130\uB9AC\xB7\uD2B9\uC218 \uD30C\uC77C\uC740 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
25382
|
-
if (i < parts.length && await info2(
|
|
25790
|
+
if (i < parts.length && await info2(join9(target, ".git"))) fail6("\uC911\uCCA9 \uC800\uC7A5\uC18C\uB294 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
25383
25791
|
if (i === parts.length) {
|
|
25384
25792
|
if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail6("\uD558\uB4DC \uB9C1\uD06C \uB610\uB294 \uD30C\uC77C \uD06C\uAE30 \uD55C\uB3C4\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
25385
|
-
return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await
|
|
25793
|
+
return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile8(target)]));
|
|
25386
25794
|
}
|
|
25387
25795
|
}
|
|
25388
25796
|
return null;
|
|
@@ -25402,7 +25810,7 @@ function paths(value, limit = 128) {
|
|
|
25402
25810
|
return list;
|
|
25403
25811
|
}
|
|
25404
25812
|
function policyPaths(files) {
|
|
25405
|
-
const all = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", ".
|
|
25813
|
+
const all = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", ".gitignore", ".gitattributes", ".gitifact/config.json", ".tryce/config.json"]);
|
|
25406
25814
|
for (const file2 of files) {
|
|
25407
25815
|
const parts = file2.split("/");
|
|
25408
25816
|
for (let i = 1; i < parts.length; i++) for (const name of ["AGENTS.md", "CLAUDE.md", ".gitignore", ".gitattributes"]) all.add(parts.slice(0, i).join("/") + "/" + name);
|
|
@@ -25412,6 +25820,7 @@ function policyPaths(files) {
|
|
|
25412
25820
|
async function checkLegacySelection(root, selected) {
|
|
25413
25821
|
const legacyJson = /^\.(?:gitifact|tryce)\/(?:spec\/[^/]+\/tryce\.json|notes\/N-[a-f0-9-]+\.json|mode-[a-f0-9-]+\.json|config\.(?:init-1|prototype-1)\.[a-f0-9]+\.json)$/;
|
|
25414
25822
|
for (const p of selected.filter((p2) => p2.startsWith(".gitifact/") && !record2(p2) && p2 !== ".gitifact/config.json")) {
|
|
25823
|
+
if (isProductAssetPath(p)) continue;
|
|
25415
25824
|
if (!legacyJson.test(p) || await fingerprint(root, p) !== null) fail6(".gitifact\uC758 \uAD6C\uD615 \uAE30\uB85D\uC740 \uC0AD\uC81C\uB9CC \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4: " + p);
|
|
25416
25825
|
}
|
|
25417
25826
|
for (const p of selected.filter((p2) => p2.startsWith(".tryce/"))) {
|
|
@@ -25444,7 +25853,7 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25444
25853
|
});
|
|
25445
25854
|
const indexPath = (await git(["rev-parse", "--path-format=absolute", "--git-path", "index"])).toString("utf8").trim();
|
|
25446
25855
|
const staged = async (index) => (await git(["diff", "--cached", "--ita-visible-in-index", "--name-only", "--no-renames", "-z"], index)).toString("utf8").split("\0").filter(Boolean).sort();
|
|
25447
|
-
const busy =
|
|
25856
|
+
const busy = join10(gitDir, "gitifact-spec-commit.lock");
|
|
25448
25857
|
if (await info2(busy)) fail6("\uC774\uC804 \uCEE4\uBC0B \uC791\uC5C5 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: " + busy);
|
|
25449
25858
|
if ((await staged()).length) fail6("\uAE30\uC874 staging\uC744 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uC120\uD0DD \uBC94\uC704\uB97C \uC815\uB9AC\uD55C \uD6C4 \uB2E4\uC2DC \uACC4\uD68D\uD558\uC138\uC694.");
|
|
25450
25859
|
const request = object3(input2);
|
|
@@ -25486,8 +25895,8 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25486
25895
|
if (data2.index !== hash2(await optional2(indexPath) ?? Buffer.alloc(0)) || (await staged()).length) fail6("\uACC4\uD68D \uC911 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25487
25896
|
return { outcome: "planned", plan: { ...data2, digest: seal(data2) }, committed: false };
|
|
25488
25897
|
}
|
|
25489
|
-
const { digest:
|
|
25490
|
-
if (request.kind !== "spec-preview-commit" || typeof
|
|
25898
|
+
const { digest: digest2, ...data } = request;
|
|
25899
|
+
if (request.kind !== "spec-preview-commit" || typeof digest2 !== "string" || digest2 !== seal(data)) fail6("\uCEE4\uBC0B \uACC4\uD68D\uC774 \uC190\uC0C1\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uACC4\uD68D\uD558\uC138\uC694.");
|
|
25491
25900
|
plan = request;
|
|
25492
25901
|
if (plan.root !== root || !Array.isArray(plan.files) || !Array.isArray(plan.context)) fail6("\uACC4\uD68D\uC758 \uC800\uC7A5\uC18C \uB610\uB294 \uD30C\uC77C \uBC94\uC704\uAC00 \uB2E4\uB985\uB2C8\uB2E4.");
|
|
25493
25902
|
const selected = paths(plan.files.map((f) => f.path));
|
|
@@ -25503,16 +25912,16 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25503
25912
|
const before = await reader.baseline();
|
|
25504
25913
|
const original = await optional2(indexPath);
|
|
25505
25914
|
await mkdir4(busy);
|
|
25506
|
-
const draftLock =
|
|
25915
|
+
const draftLock = join10(gitDir, "gitifact-spec-preview.lock");
|
|
25507
25916
|
let draftOwned = false;
|
|
25508
25917
|
let indexLock;
|
|
25509
25918
|
let uncertain = false;
|
|
25510
25919
|
let commitStarted = false;
|
|
25511
|
-
const temporary =
|
|
25920
|
+
const temporary = join10(gitDir, "gitifact-commit-index-" + randomUUID6());
|
|
25512
25921
|
const checkFiles = async () => {
|
|
25513
25922
|
for (const f of [...plan.files, ...plan.context]) if (f.hash !== await fingerprint(root, f.path)) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uD30C\uC77C\xB7\uC815\uCC45\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + f.path);
|
|
25514
25923
|
if ((await readLockedPreviewState(root)).stamp !== object3(plan.verification).stamp) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uBA85\uC138 \uC9D1\uD569\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25515
|
-
for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) if (await info2(
|
|
25924
|
+
for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) if (await info2(join10(gitDir, name))) fail6("Git \uD1B5\uD569 \uC791\uC5C5\uC774 \uC2DC\uC791\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25516
25925
|
};
|
|
25517
25926
|
try {
|
|
25518
25927
|
await verifyPreparedPreview(cwd, plan.verification, false);
|
|
@@ -25520,7 +25929,7 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25520
25929
|
draftOwned = true;
|
|
25521
25930
|
indexLock = await open4(indexPath + ".lock", "wx", 384);
|
|
25522
25931
|
if (plan.index !== hash2(await optional2(indexPath) ?? Buffer.alloc(0)) || (await staged()).length) fail6("\uC7A0\uAE08 \uC911 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25523
|
-
await writeFile2(
|
|
25932
|
+
await writeFile2(join10(busy, "recovery.json"), JSON.stringify({ before, originalIndex: original?.toString("base64") ?? null, temporary, plan }));
|
|
25524
25933
|
if (original) await writeFile2(temporary, original, { flag: "wx" });
|
|
25525
25934
|
else await git(["read-tree", "--empty"], temporary);
|
|
25526
25935
|
await git(["add", "--", ...selected], temporary);
|
|
@@ -25528,7 +25937,7 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25528
25937
|
if (!actual.length || actual.some((p) => !selected.includes(p))) fail6("\uBE48 \uCEE4\uBC0B \uB610\uB294 \uC120\uD0DD \uBC16 \uD30C\uC77C\uC774 \uAC10\uC9C0\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25529
25938
|
for (const file2 of plan.files.filter((f) => record2(f.path) && f.hash !== null)) {
|
|
25530
25939
|
const stagedBytes = await git(["show", ":" + file2.path], temporary);
|
|
25531
|
-
if (!stagedBytes.equals(await
|
|
25940
|
+
if (!stagedBytes.equals(await readFile9(join10(root, file2.path)))) fail6("Git \uD544\uD130\uAC00 \uBA85\uC138\xB7\uC774\uC720 \uC6D0\uBB38\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25532
25941
|
}
|
|
25533
25942
|
await checkFiles();
|
|
25534
25943
|
if (JSON.stringify(before) !== JSON.stringify(await reader.baseline())) fail6("HEAD\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
@@ -25543,7 +25952,7 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25543
25952
|
if (actualTree !== tree || after.branch !== before.branch || JSON.stringify(parents) !== JSON.stringify(before.head ? [before.head] : []) || trailers.some((t) => !message.split(/\r?\n/).includes(t))) fail6("\uCEE4\uBC0B\uC740 \uC2E4\uD589\uB410\uC9C0\uB9CC \uD6C5\xB7\uB3D9\uC2DC \uC791\uC5C5\uC774 \uACB0\uACFC\uB97C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25544
25953
|
if (hash2(await optional2(indexPath) ?? Buffer.alloc(0)) !== plan.index) fail6("\uCEE4\uBC0B \uD6C4 \uC6D0\uB798 index \uBCC0\uACBD\uC744 \uAC10\uC9C0\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25545
25954
|
if ((await git(["write-tree"], temporary)).toString("utf8").trim() !== tree) fail6("\uD6C5\uC774 \uCEE4\uBC0B \uD6C4 staging\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25546
|
-
await indexLock.writeFile(await
|
|
25955
|
+
await indexLock.writeFile(await readFile9(temporary));
|
|
25547
25956
|
await indexLock.sync();
|
|
25548
25957
|
await indexLock.close();
|
|
25549
25958
|
await rename3(indexPath + ".lock", indexPath);
|
|
@@ -25567,7 +25976,7 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25567
25976
|
});
|
|
25568
25977
|
await unlink4(temporary).catch(() => {
|
|
25569
25978
|
});
|
|
25570
|
-
await unlink4(
|
|
25979
|
+
await unlink4(join10(busy, "recovery.json")).catch(() => {
|
|
25571
25980
|
});
|
|
25572
25981
|
await rmdir2(busy);
|
|
25573
25982
|
if (draftOwned) await rmdir2(draftLock);
|
|
@@ -25576,9 +25985,9 @@ async function previewCommit(cwd, action, input2) {
|
|
|
25576
25985
|
}
|
|
25577
25986
|
|
|
25578
25987
|
// src/commands/spec-commit.ts
|
|
25579
|
-
import { createHash as
|
|
25580
|
-
import { mkdir as mkdir5, open as open5, readFile as
|
|
25581
|
-
import { join as
|
|
25988
|
+
import { createHash as createHash7, randomUUID as randomUUID7 } from "node:crypto";
|
|
25989
|
+
import { mkdir as mkdir5, open as open5, readFile as readFile10, rename as rename4, rmdir as rmdir3, unlink as unlink5, writeFile as writeFile3 } from "node:fs/promises";
|
|
25990
|
+
import { join as join11 } from "node:path";
|
|
25582
25991
|
var fields = ["reasons", "paths", "message", "authorization", "requirements", "policyFiles", "expected"];
|
|
25583
25992
|
var integration = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"];
|
|
25584
25993
|
var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -25617,18 +26026,18 @@ async function specCommit(cwd, input2, dryRun) {
|
|
|
25617
26026
|
...stdin ? { input: stdin } : {}
|
|
25618
26027
|
});
|
|
25619
26028
|
const staged = async (index) => (await git(["diff", "--cached", "--ita-visible-in-index", "--name-only", "--no-renames", "-z"], index)).toString("utf8").split("\0").filter(Boolean).sort();
|
|
25620
|
-
const busy =
|
|
26029
|
+
const busy = join11(gitDir, "gitifact-spec-commit.lock");
|
|
25621
26030
|
await failOnLegacyLock(gitDir);
|
|
25622
26031
|
if (await info2(busy)) fail6("\uC774\uC804 \uCEE4\uBC0B \uC791\uC5C5 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: " + busy);
|
|
25623
26032
|
if ((await staged()).length) fail6("\uAE30\uC874 staging\uC744 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uC120\uD0DD \uBC94\uC704\uB97C \uC815\uB9AC\uD55C \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
|
|
25624
26033
|
const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
|
|
25625
26034
|
const current = c.working;
|
|
25626
26035
|
if (request.expected !== void 0 && request.expected !== previewExpected(c.base, current.stamp)) fail6("\uC785\uB825\uC774 \uC624\uB798\uB410\uC2B5\uB2C8\uB2E4. changes\uBD80\uD130 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.");
|
|
25627
|
-
const reasons = request.reasons ??
|
|
25628
|
-
const prepared = prepareSpecPreview(c.
|
|
26036
|
+
const reasons = request.reasons ?? pendingPreviewReasons(c.bundle, current.bundle).map((h) => ({ requirements: h.requirements, ...h.designs ? { designs: h.designs } : {}, ...h.documents ? { documents: h.documents } : {}, reason: h.reason }));
|
|
26037
|
+
const prepared = prepareSpecPreview(c.bundle, current.bundle, c.files, current.files, reasons, () => generatePreviewId("H"));
|
|
25629
26038
|
const blobId = (text2) => {
|
|
25630
26039
|
const bytes = Buffer.from(text2);
|
|
25631
|
-
return
|
|
26040
|
+
return createHash7(objectFormat).update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
|
|
25632
26041
|
};
|
|
25633
26042
|
const present = [...current.files.keys()];
|
|
25634
26043
|
if (present.some((p) => p.includes("\n"))) fail6("\uC904\uBC14\uAFC8\uC774 \uD3EC\uD568\uB41C \uBA85\uC138 \uACBD\uB85C\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
@@ -25666,8 +26075,12 @@ async function specCommit(cwd, input2, dryRun) {
|
|
|
25666
26075
|
await c.recheck();
|
|
25667
26076
|
return { outcome: "dry-run", committed: false, ...summary };
|
|
25668
26077
|
}
|
|
25669
|
-
const trailers = [
|
|
25670
|
-
|
|
26078
|
+
const trailers = [
|
|
26079
|
+
...requirements.map((id) => "Gitifact-Req: " + id),
|
|
26080
|
+
...prepared.changes.filter((c2) => c2.kind === "design").map((c2) => "Gitifact-Design: " + c2.id),
|
|
26081
|
+
...prepared.changes.filter((c2) => c2.kind === "product" || c2.kind === "guide").map((c2) => "Gitifact-Doc: " + c2.id)
|
|
26082
|
+
];
|
|
26083
|
+
const temporary = join11(gitDir, "gitifact-commit-index-" + randomUUID7());
|
|
25671
26084
|
let owned = false;
|
|
25672
26085
|
let uncertain = false;
|
|
25673
26086
|
let indexLock;
|
|
@@ -25683,12 +26096,12 @@ async function specCommit(cwd, input2, dryRun) {
|
|
|
25683
26096
|
await previewTransaction(cwd, current.stamp, async () => ({ writes, data: {}, recheck: c.recheck }), rename4, async (published) => {
|
|
25684
26097
|
let commitStarted = false;
|
|
25685
26098
|
try {
|
|
25686
|
-
const
|
|
26099
|
+
const lock = indexLock = await open5(indexPath + ".lock", "wx", 384);
|
|
25687
26100
|
const original = await optional2(indexPath);
|
|
25688
26101
|
if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail6("\uC2E4\uD589 \uC911 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25689
26102
|
const locked = await hashes([.../* @__PURE__ */ new Set([...selected, ...contextPaths])]);
|
|
25690
26103
|
for (const [path3, value] of started) if (locked.get(path3) !== value) fail6("\uC2E4\uD589 \uC911 \uD30C\uC77C\xB7\uC815\uCC45\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25691
|
-
await writeFile3(
|
|
26104
|
+
await writeFile3(join11(busy, "recovery.json"), JSON.stringify({ before: c.base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected, message, requirements }));
|
|
25692
26105
|
if (original) await writeFile3(temporary, original, { flag: "wx" });
|
|
25693
26106
|
else await git(["read-tree", "--empty"], temporary);
|
|
25694
26107
|
await git(["add", "--", ...stageable], temporary);
|
|
@@ -25704,7 +26117,7 @@ async function specCommit(cwd, input2, dryRun) {
|
|
|
25704
26117
|
const size = Number(header[2]);
|
|
25705
26118
|
if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail6("staging\uB41C \uBA85\uC138\xB7\uC774\uC720\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25706
26119
|
const staged2 = output2.subarray(end + 1, end + 1 + size);
|
|
25707
|
-
const file2 = await
|
|
26120
|
+
const file2 = await readFile10(join11(root, path3));
|
|
25708
26121
|
if (!staged2.equals(file2) && !staged2.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail6("Git \uD544\uD130\uAC00 \uBA85\uC138\xB7\uC774\uC720 \uC6D0\uBB38\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25709
26122
|
offset = end + size + 2;
|
|
25710
26123
|
}
|
|
@@ -25712,7 +26125,7 @@ async function specCommit(cwd, input2, dryRun) {
|
|
|
25712
26125
|
const again = await hashes([...locked.keys()]);
|
|
25713
26126
|
for (const [path3, value] of locked) if (again.get(path3) !== value) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uD30C\uC77C\xB7\uC815\uCC45\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4: " + path3);
|
|
25714
26127
|
if ((await readLockedPreviewState(root)).stamp !== published.stamp) fail6("\uCEE4\uBC0B \uC900\uBE44 \uC911 \uBA85\uC138 \uC9D1\uD569\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25715
|
-
for (const name of integration) if (await info2(
|
|
26128
|
+
for (const name of integration) if (await info2(join11(gitDir, name))) fail6("Git \uD1B5\uD569 \uC791\uC5C5\uC774 \uC2DC\uC791\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25716
26129
|
if (!same2(c.base, await c.reader.baseline())) fail6("HEAD\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.");
|
|
25717
26130
|
const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
|
|
25718
26131
|
commitStarted = true;
|
|
@@ -25727,9 +26140,9 @@ async function specCommit(cwd, input2, dryRun) {
|
|
|
25727
26140
|
if (headers[0] !== "tree " + tree || after.branch !== c.base.branch || !same2(parents, c.base.head ? [c.base.head] : []) || trailers.some((t) => !body.split(/\r?\n/).includes(t))) fail6("\uCEE4\uBC0B\uC740 \uC2E4\uD589\uB410\uC9C0\uB9CC \uD6C5\xB7\uB3D9\uC2DC \uC791\uC5C5\uC774 \uACB0\uACFC\uB97C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25728
26141
|
if (hash2(await optional2(indexPath) ?? Buffer.alloc(0)) !== indexHash) fail6("\uCEE4\uBC0B \uD6C4 \uC6D0\uB798 index \uBCC0\uACBD\uC744 \uAC10\uC9C0\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25729
26142
|
if ((await git(["write-tree"], temporary)).toString("utf8").trim() !== tree) fail6("\uD6C5\uC774 \uCEE4\uBC0B \uD6C4 staging\uC744 \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25730
|
-
await
|
|
25731
|
-
await
|
|
25732
|
-
await
|
|
26143
|
+
await lock.writeFile(await readFile10(temporary));
|
|
26144
|
+
await lock.sync();
|
|
26145
|
+
await lock.close();
|
|
25733
26146
|
await rename4(indexPath + ".lock", indexPath);
|
|
25734
26147
|
indexLock = void 0;
|
|
25735
26148
|
committed = { commit: after.head, paths: actual };
|
|
@@ -25754,7 +26167,7 @@ async function specCommit(cwd, input2, dryRun) {
|
|
|
25754
26167
|
await unlink5(temporary).catch(() => {
|
|
25755
26168
|
});
|
|
25756
26169
|
if (owned) {
|
|
25757
|
-
await unlink5(
|
|
26170
|
+
await unlink5(join11(busy, "recovery.json")).catch(() => {
|
|
25758
26171
|
});
|
|
25759
26172
|
await rmdir3(busy);
|
|
25760
26173
|
}
|
|
@@ -25771,7 +26184,7 @@ async function execute(action, options) {
|
|
|
25771
26184
|
try {
|
|
25772
26185
|
const guard = specPreviewReader(process.cwd());
|
|
25773
26186
|
const { root, gitDir, objectFormat } = await guard.location();
|
|
25774
|
-
for (const marker2 of operations) if (await fileInfo(
|
|
26187
|
+
for (const marker2 of operations) if (await fileInfo(join12(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker2);
|
|
25775
26188
|
if (await guard.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", "Git \uCDA9\uB3CC\uC744 \uBA3C\uC800 \uD574\uACB0\uD558\uC138\uC694.");
|
|
25776
26189
|
const raw = await readConfigFile(root);
|
|
25777
26190
|
if (raw === void 0) throw new SpecPreviewError("\uBA3C\uC800 gitifact init\uC73C\uB85C \uCD08\uAE30\uD654\uD558\uC138\uC694.");
|
|
@@ -25783,9 +26196,9 @@ async function execute(action, options) {
|
|
|
25783
26196
|
if (action === "working") result = await readWorkingPreview(process.cwd());
|
|
25784
26197
|
else if (action === "changes") result = await readFinalPreviewChanges(process.cwd());
|
|
25785
26198
|
else {
|
|
25786
|
-
const
|
|
25787
|
-
if (!
|
|
25788
|
-
const bytes = await
|
|
26199
|
+
const source = await stat2(options.file);
|
|
26200
|
+
if (!source.isFile() || source.size > 1024 * 1024) throw new SpecPreviewError("\uC785\uB825\uC740 1 MiB \uC774\uD558\uC758 JSON \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
|
|
26201
|
+
const bytes = await readFile11(options.file);
|
|
25789
26202
|
if (bytes.length > 1024 * 1024) throw new SpecPreviewError("\uC785\uB825 \uD06C\uAE30\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
25790
26203
|
let input2;
|
|
25791
26204
|
try {
|
|
@@ -25801,12 +26214,12 @@ async function execute(action, options) {
|
|
|
25801
26214
|
const reader = specPreviewReader(process.cwd());
|
|
25802
26215
|
if (action === "read") {
|
|
25803
26216
|
const commit = await reader.resolve(options.ref ?? "HEAD");
|
|
25804
|
-
const
|
|
25805
|
-
process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs }) + "\n");
|
|
26217
|
+
const bundle = await reader.readBundle(commit);
|
|
26218
|
+
process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs: bundle.specs, documents: bundle.documents }) + "\n");
|
|
25806
26219
|
} else {
|
|
25807
26220
|
const [from, to] = await Promise.all([reader.resolve(options.from), reader.resolve(options.to)]);
|
|
25808
|
-
const [before, after] = await Promise.all([reader.
|
|
25809
|
-
process.stdout.write(JSON.stringify({ ...envelope, ok: true, from, to, ...
|
|
26221
|
+
const [before, after] = await Promise.all([reader.readBundle(from), reader.readBundle(to)]);
|
|
26222
|
+
process.stdout.write(JSON.stringify({ ...envelope, ok: true, from, to, ...comparePreviewBundles(before, after) }) + "\n");
|
|
25810
26223
|
}
|
|
25811
26224
|
} catch (error62) {
|
|
25812
26225
|
const known = error62 instanceof SpecPreviewError || error62 instanceof RepositoryReadError || error62 instanceof InitError;
|
|
@@ -25816,11 +26229,11 @@ async function execute(action, options) {
|
|
|
25816
26229
|
}
|
|
25817
26230
|
|
|
25818
26231
|
// src/commands/migrate.ts
|
|
25819
|
-
import { lstat as
|
|
25820
|
-
import { join as
|
|
26232
|
+
import { lstat as lstat7, readdir as readdir4, readFile as readFile12, rename as rename5, writeFile as writeFile4, unlink as unlink6 } from "node:fs/promises";
|
|
26233
|
+
import { join as join13 } from "node:path";
|
|
25821
26234
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
25822
26235
|
var operations2 = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"];
|
|
25823
|
-
var info3 = (path3) =>
|
|
26236
|
+
var info3 = (path3) => lstat7(path3).catch((e) => {
|
|
25824
26237
|
if (e.code === "ENOENT") return void 0;
|
|
25825
26238
|
throw e;
|
|
25826
26239
|
});
|
|
@@ -25831,18 +26244,18 @@ var fail7 = (code, message) => {
|
|
|
25831
26244
|
async function migrateStore(cwd, dryRun = false) {
|
|
25832
26245
|
const reader = specPreviewReader(cwd);
|
|
25833
26246
|
const { root, gitDir } = await reader.location();
|
|
25834
|
-
for (const marker3 of operations2) if (await info3(
|
|
26247
|
+
for (const marker3 of operations2) if (await info3(join13(gitDir, marker3))) fail7("GIT_OPERATION_IN_PROGRESS", "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: " + marker3);
|
|
25835
26248
|
if (await reader.hasUnmerged()) fail7("GIT_OPERATION_IN_PROGRESS", "Git \uCDA9\uB3CC\uC744 \uBA3C\uC800 \uD574\uACB0\uD558\uC138\uC694.");
|
|
25836
|
-
for (const name of [...LEGACY_LOCKS, "gitifact-spec-preview.lock", "gitifact-spec-commit.lock"]) if (await info3(
|
|
25837
|
-
const from =
|
|
25838
|
-
const to =
|
|
26249
|
+
for (const name of [...LEGACY_LOCKS, "gitifact-spec-preview.lock", "gitifact-spec-commit.lock"]) if (await info3(join13(gitDir, name))) fail7("LOCKED", "\uC7A0\uAE08 \uB610\uB294 \uBCF5\uAD6C \uC790\uB8CC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: " + join13(gitDir, name));
|
|
26250
|
+
const from = join13(root, ".tryce");
|
|
26251
|
+
const to = join13(root, ".gitifact");
|
|
25839
26252
|
const fromInfo = await info3(from);
|
|
25840
26253
|
if (await info3(to)) fail7("ALREADY_MIGRATED", ".gitifact\uAC00 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. .tryce\uAC00 \uB0A8\uC544 \uC788\uC73C\uBA74 \uB0B4\uC6A9\uC744 \uBE44\uAD50\uD55C \uB4A4 \uC9C1\uC811 \uC815\uB9AC\uD558\uC138\uC694.");
|
|
25841
26254
|
if (!fromInfo) return fail7("NOTHING_TO_MIGRATE", ".tryce \uC800\uC7A5\uC18C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
25842
26255
|
if (!fromInfo.isDirectory() || fromInfo.isSymbolicLink()) fail7("PATH_CONFLICT", ".tryce\uB294 \uB9C1\uD06C\uAC00 \uC544\uB2CC \uC77C\uBC18 \uB514\uB809\uD130\uB9AC\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
25843
|
-
const configInfo = await info3(
|
|
26256
|
+
const configInfo = await info3(join13(from, "config.json"));
|
|
25844
26257
|
if (!configInfo?.isFile() || configInfo.isSymbolicLink()) fail7("NOT_INITIALIZED", ".tryce/config.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
25845
|
-
const config2 = parseManagedConfig(decode4(await
|
|
26258
|
+
const config2 = parseManagedConfig(decode4(await readFile12(join13(from, "config.json"))));
|
|
25846
26259
|
if (!("schemaVersion" in config2)) fail7("UNSUPPORTED_FORMAT", "\uAD6C\uD615 JSON \uD615\uC2DD\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uAE30\uB85D\uC744 \uBCF4\uC874\uD558\uACE0 \uBCC4\uB3C4 \uC808\uCC28\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
25847
26260
|
const top = (await readdir4(from)).sort();
|
|
25848
26261
|
if (top.some((name) => name !== "config.json" && name !== "spec")) fail7("UNSUPPORTED_FORMAT", ".tryce\uC5D0 config.json\xB7spec \uC678 \uD56D\uBAA9\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
|
|
@@ -25851,20 +26264,20 @@ async function migrateStore(cwd, dryRun = false) {
|
|
|
25851
26264
|
const legacyRecords = [];
|
|
25852
26265
|
const marker2 = /^<!-- tryce-(spec|req|design|ref): /;
|
|
25853
26266
|
if (top.includes("spec")) {
|
|
25854
|
-
for (const feature2 of (await readdir4(
|
|
25855
|
-
const dir =
|
|
26267
|
+
for (const feature2 of (await readdir4(join13(from, "spec"))).sort()) {
|
|
26268
|
+
const dir = join13(from, "spec", feature2);
|
|
25856
26269
|
const stat3 = await info3(dir);
|
|
25857
26270
|
if (!stat3?.isDirectory() || stat3.isSymbolicLink()) fail7("UNSUPPORTED_FORMAT", "\uBA85\uC138 \uD3F4\uB354\uAC00 \uC544\uB2D9\uB2C8\uB2E4: " + feature2);
|
|
25858
26271
|
for (const name of (await readdir4(dir)).sort()) {
|
|
25859
26272
|
if (!["requirements.md", "design.md", "history.jsonl", "tryce.json"].includes(name)) fail7("UNSUPPORTED_FORMAT", "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uD30C\uC77C\uC785\uB2C8\uB2E4: .tryce/spec/" + feature2 + "/" + name);
|
|
25860
|
-
const fileStat = await info3(
|
|
26273
|
+
const fileStat = await info3(join13(dir, name));
|
|
25861
26274
|
if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.nlink !== 1 || fileStat.size > 1024 * 1024) fail7("UNSUPPORTED_FORMAT", "\uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2C8\uAC70\uB098 1 MiB\uB97C \uB118\uC2B5\uB2C8\uB2E4: " + name);
|
|
25862
26275
|
const newPath = `.gitifact/spec/${feature2}/${name}`;
|
|
25863
26276
|
if (name === "tryce.json") {
|
|
25864
26277
|
legacyRecords.push(newPath);
|
|
25865
26278
|
continue;
|
|
25866
26279
|
}
|
|
25867
|
-
const text2 = decode4(await
|
|
26280
|
+
const text2 = decode4(await readFile12(join13(dir, name)));
|
|
25868
26281
|
if (name === "history.jsonl") {
|
|
25869
26282
|
files.set(newPath, text2);
|
|
25870
26283
|
continue;
|
|
@@ -25902,15 +26315,15 @@ async function migrateStore(cwd, dryRun = false) {
|
|
|
25902
26315
|
next: [
|
|
25903
26316
|
"\uBCC0\uACBD\uC744 \uD655\uC778\uD55C \uB4A4 gitifact spec commit\uC73C\uB85C .tryce \uC0AD\uC81C\uC640 .gitifact \uCD94\uAC00\uB97C \uD568\uAED8 \uCEE4\uBC0B\uD558\uC138\uC694.",
|
|
25904
26317
|
...legacyRecords.length ? ["\uAD6C\uD615 JSON \uBA85\uC138(tryce.json)\uB294 \uC77D\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uB0B4\uC6A9\uC744 Markdown \uBA85\uC138\uB85C \uC62E\uAE34 \uB4A4 \uC0AD\uC81C\uB97C spec commit\uC5D0 \uC120\uD0DD\uD558\uC138\uC694."] : [],
|
|
25905
|
-
".agents/skills/tryce-workflow\uC640 .claude \uBCF5\uC0AC\uBCF8\uC774 \uC788\uC73C\uBA74 \uC815\uB9AC\uD558\uACE0 gitifact
|
|
26318
|
+
".agents/skills/tryce-workflow\uC640 .claude \uBCF5\uC0AC\uBCF8\uC774 \uC788\uC73C\uBA74 \uC815\uB9AC\uD558\uACE0 gitifact init\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 AGENTS.md \uB4F1\uC758 GITIFACT \uBE14\uB85D\uC744 \uAC31\uC2E0\uD558\uC138\uC694.",
|
|
25906
26319
|
".gitignore\xB7AGENTS.md\xB7CLAUDE.md\uC758 .tryce\xB7tryce-workflow \uC5B8\uAE09\uC744 \uC0C8 \uC774\uB984\uC73C\uB85C \uB9DE\uCD94\uC138\uC694."
|
|
25907
26320
|
]
|
|
25908
26321
|
});
|
|
25909
26322
|
if (dryRun) return result("planned");
|
|
25910
26323
|
await rename5(from, to);
|
|
25911
26324
|
for (const r of rewrites) {
|
|
25912
|
-
const full =
|
|
25913
|
-
const temp =
|
|
26325
|
+
const full = join13(root, r.path);
|
|
26326
|
+
const temp = join13(root, ".gitifact", "spec", ".gitifact-migrate-" + randomBytes2(8).toString("hex"));
|
|
25914
26327
|
await writeFile4(temp, r.content, { flag: "wx" });
|
|
25915
26328
|
try {
|
|
25916
26329
|
await rename5(temp, full);
|
|
@@ -25933,20 +26346,16 @@ async function runMigrate(options) {
|
|
|
25933
26346
|
}
|
|
25934
26347
|
|
|
25935
26348
|
// src/main.ts
|
|
25936
|
-
var program2 = new Command().name("gitifact").description("\uD504\uB85C\uC81D\uD2B8\uC758 \uC694\uAD6C\uC0AC\uD56D\uACFC \uACB0\uC815 \uC774\uB825\uC744 Git\uC5D0 \uB0A8\uAE30\uB294 \uB3C4\uAD6C").version("0.
|
|
26349
|
+
var program2 = new Command().name("gitifact").description("\uD504\uB85C\uC81D\uD2B8\uC758 \uC694\uAD6C\uC0AC\uD56D\uACFC \uACB0\uC815 \uC774\uB825\uC744 Git\uC5D0 \uB0A8\uAE30\uB294 \uB3C4\uAD6C").version("0.3.0").allowExcessArguments(false).addHelpText("after", "\n\uC804\uCCB4 \uC774\uB825 \uAC80\uC0AC\uC640 GUI \uC694\uAD6C\uC0AC\uD56D \uD3B8\uC9D1\uC740 \uC544\uC9C1 \uC81C\uACF5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.").action(() => program2.outputHelp());
|
|
25937
26350
|
program2.command("status").description("\uD604\uC7AC checkout\uC758 Git \uC0C1\uD0DC \uC870\uD68C (gitifact \uAC80\uC0AC \uBBF8\uC2E4\uD589)").allowExcessArguments(false).addOption(new Option("--format <format>", "\uCD9C\uB825 \uD615\uC2DD").choices(["json", "text"]).default("json")).action(async (options) => {
|
|
25938
26351
|
await runStatus(options.format);
|
|
25939
26352
|
});
|
|
25940
26353
|
program2.command("browser").description("\uD604\uC7AC checkout\uC758 Git \uC0C1\uD0DC\uB97C \uB85C\uCEEC \uBE0C\uB77C\uC6B0\uC800\uB85C \uC870\uD68C").allowExcessArguments(false).option("--port <port>", "\uC218\uC2E0 \uD3EC\uD2B8 (0\uC740 \uC790\uB3D9 \uC120\uD0DD)", parsePort, 0).option("--dev", "Vite \uAC1C\uBC1C \uC11C\uBC84 Origin \uD5C8\uC6A9").action(runBrowser);
|
|
25941
|
-
program2.command("init").description("\uD504\uB85C\uC81D\uD2B8 \uC124\uC815\
|
|
26354
|
+
program2.command("init").description("\uD504\uB85C\uC81D\uD2B8 \uC124\uC815\xB7\uAE30\uC900\uC120 \uC0DD\uC131\uACFC \uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68 \uD30C\uC77C\uC758 GITIFACT \uBE14\uB85D \uC124\uCE58 (\uC7AC\uC2E4\uD589 \uC2DC \uBE14\uB85D \uAC31\uC2E0)").allowExcessArguments(false).option("--dry-run", "\uD30C\uC77C\uC744 \uB9CC\uB4E4\uC9C0 \uC54A\uACE0 \uCD08\uAE30\uD654 \uACC4\uD68D \uD655\uC778").addOption(new Option("--agent <agent>", "\uC9C0\uCE68 \uD30C\uC77C \uB300\uC0C1 (\uAE30\uBCF8: \uAE30\uC874 \uD30C\uC77C \uC804\uBD80, \uC5C6\uC73C\uBA74 AGENTS.md)").choices([...agentPresetNames])).addOption(new Option("--remove-agents", "GITIFACT \uBE14\uB85D\uC744 \uC81C\uAC70").conflicts("skipAgents")).option("--skip-agents", "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC74C").addOption(new Option("--format <format>", "\uCD9C\uB825 \uD615\uC2DD").choices(["json", "text"]).default("json")).action((options) => runInit(options, "0.3.0"));
|
|
26355
|
+
program2.command("docs").description("\uC5D0\uC774\uC804\uD2B8\uC6A9 \uC791\uC5C5 \uC9C0\uCE68 \uCD9C\uB825 (\uC8FC\uC81C \uC5C6\uC774 \uC2E4\uD589\uD558\uBA74 \uBAA9\uB85D)").argument("[topic]", "workflow, spec, design, product, commit \uC911 \uD558\uB098").allowExcessArguments(false).action(async (topic) => {
|
|
26356
|
+
await runDocs(topic);
|
|
26357
|
+
});
|
|
25942
26358
|
program2.command("migrate").description(".tryce \uC800\uC7A5\uC18C\uB97C .gitifact\uB85C \uC804\uD658 (\uACBD\uB85C\uC640 \uB9C8\uCEE4\uB9CC \uBC14\uAFB8\uACE0 ID\xB7\uC774\uC720\xB7\uC774\uB825\uC740 \uBCF4\uC874)").allowExcessArguments(false).option("--dry-run", "\uD30C\uC77C\uC744 \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uC804\uD658 \uACC4\uD68D \uD655\uC778").action(runMigrate);
|
|
25943
|
-
var outputOption = () => new Option("--format <format>", "\uCD9C\uB825 \uD615\uC2DD").choices(["json", "text"]).default("json");
|
|
25944
|
-
var skills = program2.command("skills").description("\uD504\uB85C\uC81D\uD2B8 \uC2A4\uD0AC \uC6D0\uBCF8 \uC124\uCE58\uC640 \uB85C\uCEEC \uBCF5\uC0AC\uBCF8 \uAD00\uB9AC");
|
|
25945
|
-
for (const action of ["install", "sync", "remove"]) {
|
|
25946
|
-
const command = skills.command(action).allowExcessArguments(false).option("--dry-run", "\uD30C\uC77C\uC744 \uC4F0\uC9C0 \uC54A\uACE0 \uACC4\uD68D \uD655\uC778").addOption(outputOption());
|
|
25947
|
-
if (action !== "remove") command.addOption(new Option("--agent <agent>", "\uC0AC\uC6A9\uD560 \uC5D0\uC774\uC804\uD2B8").choices(["codex", "claude"]));
|
|
25948
|
-
command.action((options) => runSkills(action, options));
|
|
25949
|
-
}
|
|
25950
26359
|
var spec = program2.command("spec").description("Markdown \uBA85\uC138 \uC791\uC131\xB7\uC870\uD68C\xB7Git \uAE30\uB85D");
|
|
25951
26360
|
var replaced = "Deprecated: 0.6.0\uC5D0\uC11C \uC81C\uAC70 \uC608\uC815. spec commit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.";
|
|
25952
26361
|
spec.command("commit").description("\uBCC0\uACBD \uC774\uC720 \uAE30\uB85D\uACFC \uAD00\uB828 \uD30C\uC77C \uCEE4\uBC0B\uC744 \uD55C \uBC88\uC5D0 \uC2E4\uD589").allowExcessArguments(false).requiredOption("--file <path>", "reasons\xB7paths\xB7message\xB7authorization\uC744 \uB2F4\uC740 JSON \uD30C\uC77C").option("--dry-run", "\uD30C\uC77C\uC744 \uC4F0\uAC70\uB098 \uCEE4\uBC0B\uD558\uC9C0 \uC54A\uACE0 \uACB0\uACFC\uB9CC \uD655\uC778").action((o) => runSpecPreview("commit", o));
|