boxglow 0.5.0 → 0.5.2
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 +6 -4
- package/bin/boxglow.js +2567 -378
- package/dist/assets/index-B0rLWujQ.css +1 -0
- package/dist/assets/index-qzdNjZOL.js +3465 -0
- package/dist/index.html +2 -2
- package/docs/AGENTS_SNIPPET.md +4 -0
- package/package.json +1 -1
- package/dist/assets/index-BcMBIZRu.css +0 -1
- package/dist/assets/index-CKeLrulC.js +0 -3464
package/bin/boxglow.js
CHANGED
|
@@ -57,6 +57,161 @@ var workflow = {
|
|
|
57
57
|
"\u8AAC\u660E\u306E\u898B\u76F4\u3057\u5019\u88DC ({n})": "Descriptions to review ({n})"
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// src/i18n/en/sync.ts
|
|
61
|
+
var sync = {
|
|
62
|
+
"\u540C\u671F\u5148\u304C\u6C7A\u307E\u3063\u3066\u3044\u307E\u305B\u3093\u3002\u521D\u3081\u3066\u7D50\u3073\u4ED8\u3051\u308B\u3068\u304D\u306F boxglow sync --server <URL> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044": "No sync server is set. The first time, bind with boxglow sync --server <URL>",
|
|
63
|
+
"\u3053\u306E\u8A08\u753B\u306F\u8907\u6570\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u307E\u3059\u3002--server <URL> \u3067\u9078\u3093\u3067\u304F\u3060\u3055\u3044: {list}": "This plan is bound to several servers. Choose one with --server <URL>: {list}",
|
|
64
|
+
"--recover \u306B\u306F --applied (\u53CD\u6620\u6E08\u307F\u3068\u3057\u3066\u7D9A\u3051\u308B) \u304B --not-applied (\u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u3082\u306E\u3068\u3057\u3066\u7D9A\u3051\u308B) \u306E\u3069\u3061\u3089\u304B\u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044": "--recover needs either --applied (continue as applied) or --not-applied (continue as not applied)",
|
|
65
|
+
"\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F (\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u306F\u6B8B\u3057\u3066\u3042\u308A\u307E\u3059\u3002\u3082\u3046\u4E00\u5EA6 boxglow sync \u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u7D9A\u304D\u304B\u3089\u9032\u307F\u307E\u3059): {message}": "Could not reach the server (the unfinished operation is kept; run boxglow sync again to continue): {message}",
|
|
66
|
+
"\u540C\u671F\u3057\u307E\u3057\u305F (\u53D7\u3051\u53D6\u308A {pulled} \u56DE\u3001\u9001\u308A {pushed} \u56DE)\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u7248: {revision}": "Synced (pulled {pulled}, pushed {pushed}). Server revision: {revision}",
|
|
67
|
+
"\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u7248: {revision}": "Nothing to sync. Server revision: {revision}",
|
|
68
|
+
"\u8A08\u753B\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u66F8\u304D\u8FBC\u307F\u4E2D\u306E\u305F\u3081\u3001\u4ECA\u56DE\u306F\u9032\u3081\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u5C11\u3057\u5F85\u3063\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "The plan file is being written, so nothing was done this time. Wait a moment and run it again",
|
|
69
|
+
"\u3053\u306E\u8A08\u753B\u306E\u540C\u671F\u304C\u3001\u307B\u304B\u3067\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u7D42\u308F\u3063\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "Another sync of this plan is running. Run it again after it finishes",
|
|
70
|
+
"\u540C\u671F\u3092\u6B62\u3081\u307E\u3057\u305F\u3002\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3082\u30B5\u30FC\u30D0\u30FC\u3082\u5909\u3048\u3066\u3044\u307E\u305B\u3093\u3002": "Sync stopped. Neither the local file nor the server was changed.",
|
|
71
|
+
"\u624B\u5143\u3068\u30B5\u30FC\u30D0\u30FC\u3067\u3001\u540C\u3058\u9805\u76EE\u304C\u5225\u306E\u5024\u306B\u5909\u308F\u3063\u3066\u3044\u307E\u3059 ({count} \u4EF6):": "The same fields were changed to different values locally and on the server ({count}):",
|
|
72
|
+
"\u624B\u5143": "local",
|
|
73
|
+
"\u30B5\u30FC\u30D0\u30FC": "server",
|
|
74
|
+
"\u3069\u3061\u3089\u3092\u63A1\u308B\u304B\u3092\u9078\u3076\u64CD\u4F5C\u306F\u3001\u307E\u3060\u7528\u610F\u3067\u304D\u3066\u3044\u307E\u305B\u3093\u3002\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3067\u3001\u63A1\u308A\u305F\u3044\u5024\u306B\u76F4\u3057\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044 (\u30B5\u30FC\u30D0\u30FC\u3068\u540C\u3058\u5024\u306B\u3057\u305F\u9805\u76EE\u306F\u3001\u7AF6\u5408\u306B\u306A\u308A\u307E\u305B\u3093)": "Choosing a side is not available yet. Edit the local file to the values you want and run it again (a field set to the server's value is no longer a conflict)",
|
|
75
|
+
"\u624B\u5143\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4\u3092\u5408\u308F\u305B\u308B\u3068\u3001\u8A08\u753B\u3068\u3057\u3066\u6B63\u3057\u304F\u306A\u3044\u5F62\u306B\u306A\u308A\u307E\u3059: {problem}": "Combining the local and server changes gives an invalid plan: {problem}",
|
|
76
|
+
"\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3067\u3001\u3069\u3061\u3089\u304B\u306E\u5909\u66F4\u3092\u53D6\u308A\u6D88\u3057\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "Undo one of the changes in the local file and run it again",
|
|
77
|
+
"\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3001\u8A08\u753B\u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093: {problem}": "The local file is not a valid plan: {problem}",
|
|
78
|
+
"\u30B5\u30FC\u30D0\u30FC\u306E\u4E2D\u8EAB\u304C\u3001\u8A08\u753B\u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093: {problem}": "The server content is not a valid plan: {problem}",
|
|
79
|
+
"\u524D\u56DE\u305D\u308D\u3048\u305F\u3068\u304D\u306B\u5728\u3063\u305F\u8A2D\u5B9A\u304C\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u6D88\u3048\u3066\u3044\u307E\u3059: {keys}": "Settings that existed at the last sync are missing from the local file: {keys}",
|
|
80
|
+
"\u53E4\u3044\u7248\u306E Boxglow \u304C\u4FDD\u5B58\u306E\u3068\u304D\u306B\u843D\u3068\u3057\u305F\u306E\u304B\u3082\u3057\u308C\u307E\u305B\u3093 (\u66F8\u304D\u624B\u306F\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093)\u3002\u610F\u56F3\u3057\u305F\u524A\u9664\u306A\u3089\u3001\u6B21\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u305D\u306E\u524A\u9664\u3092\u9001\u308A\u307E\u3059:": "An older Boxglow may have dropped them when saving (the writer cannot be verified). If the deletion is intended, this sends it:",
|
|
81
|
+
"\u610F\u56F3\u3057\u3066\u3044\u306A\u3044\u306A\u3089\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u8A2D\u5B9A\u3092\u623B\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6 boxglow sync \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "If it is not intended, put the settings back in the local file and run boxglow sync again",
|
|
82
|
+
"\u524D\u56DE\u306E\u540C\u671F\u304C\u3001\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4\u3092\u624B\u5143\u306B\u66F8\u304F\u9014\u4E2D\u3067\u7D42\u308F\u3063\u3066\u3044\u307E\u3057\u305F\u3002\u66F8\u304D\u8FBC\u3081\u305F\u304B\u3069\u3046\u304B\u3092\u3001\u4ECA\u306E\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u306F\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3002": "The previous sync ended while writing the server's change to the local file. Whether it was written cannot be verified from the file as it is now.",
|
|
83
|
+
"\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u3001\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4 (\u7248 {revision}) \u304C\u5165\u3063\u3066\u3044\u308B\u306A\u3089:": "If the local file contains the server's change (revision {revision}):",
|
|
84
|
+
"\u5165\u3063\u3066\u3044\u306A\u3044\u306A\u3089:": "If it does not:",
|
|
85
|
+
"\u3069\u3061\u3089\u304B\u5206\u304B\u3089\u306A\u3044\u3068\u304D\u306F\u3001\u4F55\u3082\u5B9F\u884C\u305B\u305A\u306B\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u4E2D\u8EAB\u3092\u898B\u6BD4\u3079\u3066\u304F\u3060\u3055\u3044 (\u9078\u3093\u3060\u7D50\u679C\u306E\u898B\u6BD4\u3079\u306E\u8868\u793A\u306F\u3001\u307E\u3060\u7528\u610F\u3067\u304D\u3066\u3044\u307E\u305B\u3093)": "If you cannot tell, run neither and compare the local file with the server content (a preview of each outcome is not available yet)",
|
|
86
|
+
"\u30B5\u30FC\u30D0\u30FC\u306E\u5C65\u6B74\u304C\u3001\u524D\u56DE\u305D\u308D\u3048\u305F\u3068\u304D\u304B\u3089\u5909\u308F\u3063\u3066\u3044\u307E\u3059 (\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u304B\u3089\u306E\u5FA9\u65E7\u306A\u3069)\u3002\u524D\u56DE\u306E\u7D9A\u304D\u3068\u3057\u3066\u306F\u540C\u671F\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D3\u30B9\u306E\u6848\u5185\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044": "The server's history changed since the last sync (for example a restore from backup). It cannot continue from the last sync; check the service's notice",
|
|
87
|
+
"\u524D\u56DE\u306E\u9001\u4FE1\u306E\u8A18\u9332\u304C\u53E4\u3059\u304E\u308B\u3001\u307E\u305F\u306F\u65E5\u6642\u304C\u8AAD\u3081\u306A\u3044\u305F\u3081\u3001\u81EA\u52D5\u3067\u306F\u7D9A\u3051\u3089\u308C\u307E\u305B\u3093": "The record of the previous push is too old or its time is unreadable, so it cannot continue automatically",
|
|
88
|
+
"\u624B\u5143\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u4E21\u65B9\u306B\u3001\u9055\u3046\u4E2D\u8EAB\u306E\u8A08\u753B\u304C\u3042\u308A\u307E\u3059\u3002\u3069\u3061\u3089\u3092\u63A1\u308B\u304B\u3092\u9078\u3076\u64CD\u4F5C\u306F\u3001\u307E\u3060\u7528\u610F\u3067\u304D\u3066\u3044\u307E\u305B\u3093\u3002\u63A1\u3089\u306A\u3044\u5074\u3092\u5225\u306E\u5834\u6240\u3078\u9000\u907F\u3057\u3066\u304B\u3089\u3001\u7D50\u3073\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044": "Both the local file and the server hold different plans. Choosing a side is not available yet. Move the one you do not want elsewhere, then bind again",
|
|
89
|
+
"\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u3001\u3053\u306E\u8A08\u753B\u306F\u6D88\u3055\u308C\u3066\u3044\u307E\u3059 (\u81EA\u52D5\u3067\u306F\u4F5C\u308A\u76F4\u3057\u307E\u305B\u3093)": "This plan was deleted on the server (it is not recreated automatically)",
|
|
90
|
+
"\u624B\u5143\u306E\u8A08\u753B\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093: {file}": "The local plan file is missing: {file}",
|
|
91
|
+
"\u540C\u671F\u306E\u72B6\u614B\u306E\u30D5\u30A9\u30EB\u30C0\u304B\u3089\u3001\u524D\u56DE\u305D\u308D\u3048\u305F\u4E2D\u8EAB\u306E\u5199\u3057\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u7D50\u3073\u4ED8\u3051\u76F4\u3057\u304C\u5FC5\u8981\u3067\u3059": "The copy of the last synced content is missing from the sync state folder. The plan needs to be bound again",
|
|
92
|
+
"\u3053\u306E\u30D1\u30B9\u306B\u306F\u3001\u7D50\u3073\u4ED8\u3051\u305F\u3068\u304D\u3068\u306F\u5225\u306E\u8A08\u753B\u304C\u7F6E\u304B\u308C\u3066\u3044\u307E\u3059 (\u7D50\u3073\u4ED8\u3051\u305F\u8A08\u753B\u306E ID: {expected}\u3001\u4ECA\u306E\u8A08\u753B\u306E ID: {actual})\u3002\u540C\u671F\u3057\u307E\u305B\u3093": "A different plan is at this path than when it was bound (bound plan id: {expected}, current plan id: {actual}). Not syncing",
|
|
93
|
+
"--resolve / --link \u306B\u306F --prefer local (\u624B\u5143\u3092\u63A1\u308B) \u304B --prefer remote (\u30B5\u30FC\u30D0\u30FC\u3092\u63A1\u308B) \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044": "--resolve / --link need --prefer local (keep the local side) or --prefer remote (keep the server side)",
|
|
94
|
+
"\u8868\u793A\u3057\u305F\u9805\u76EE\u3092\u3001\u624B\u5143\u306E\u5024\u306B\u6C7A\u3081\u308B\u306A\u3089 (\u307B\u304B\u306E\u5909\u66F4\u306F\u4E21\u65B9\u3068\u3082\u6B8B\u308A\u307E\u3059):": "To settle the fields shown on the local values (other changes from both sides are kept):",
|
|
95
|
+
"\u30B5\u30FC\u30D0\u30FC\u306E\u5024\u306B\u6C7A\u3081\u308B\u306A\u3089:": "To settle them on the server values:",
|
|
96
|
+
"\u9805\u76EE\u3054\u3068\u306B\u9078\u3073\u5206\u3051\u305F\u3044\u3068\u304D\u306F\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3067\u63A1\u308A\u305F\u3044\u5024\u306B\u76F4\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6 boxglow sync \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044 (\u30B5\u30FC\u30D0\u30FC\u3068\u540C\u3058\u5024\u306B\u3057\u305F\u9805\u76EE\u306F\u3001\u7AF6\u5408\u306B\u306A\u308A\u307E\u305B\u3093)": "To choose field by field, edit the local file to the values you want and run boxglow sync again (a field set to the server's value is no longer a conflict)",
|
|
97
|
+
"\u610F\u56F3\u3057\u3066\u3044\u306A\u3044\u306A\u3089\u3001\u6B21\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u6D88\u3048\u305F\u8A2D\u5B9A\u3060\u3051\u3092\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u307E\u3059 (\u307B\u304B\u306E\u7DE8\u96C6\u306F\u305D\u306E\u307E\u307E\u3067\u3059):": "If it is not intended, this puts only the missing settings back into the local file (other edits are untouched):",
|
|
98
|
+
"\u524D\u56DE\u306E\u540C\u671F\u304C\u3001\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4 (\u7248 {revision}) \u3092\u624B\u5143\u306B\u66F8\u304F\u9014\u4E2D\u3067\u7D42\u308F\u3063\u3066\u3044\u307E\u3057\u305F\u3002\u66F8\u304D\u8FBC\u3081\u305F\u304B\u3069\u3046\u304B\u3092\u3001\u4ECA\u306E\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u306F\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3002": "The previous sync ended while writing the server's change (revision {revision}) to the local file. Whether it was written cannot be verified from the file as it is now.",
|
|
99
|
+
"\u7D9A\u3051\u65B9\u306F 2 \u3064\u3042\u308A\u307E\u3059\u3002\u305D\u308C\u305E\u308C\u3092\u9078\u3093\u3060\u5834\u5408\u306B\u8D77\u304D\u308B\u3053\u3068\u3092\u898B\u6BD4\u3079\u3066\u3001\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002": "There are two ways to continue. Compare what each would do, then choose.",
|
|
100
|
+
"\u3053\u306E\u7D9A\u3051\u65B9\u3067\u306F\u3001\u5225\u306E\u78BA\u8A8D\u304C\u5FC5\u8981\u306B\u306A\u3063\u3066\u3001\u3082\u3046\u4E00\u5EA6\u6B62\u307E\u308A\u307E\u3059 ({reason})": "This way needs another confirmation and stops again ({reason})",
|
|
101
|
+
"\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u5165\u308B\u5909\u66F4:": "Changes that enter the local file:",
|
|
102
|
+
"\u30B5\u30FC\u30D0\u30FC\u3078\u9001\u308B\u3053\u3068\u306B\u306A\u308B\u5909\u66F4:": "Changes that will be sent to the server:",
|
|
103
|
+
"\u306A\u3057": "none",
|
|
104
|
+
"A. \u53D7\u3051\u53D6\u308A\u306F\u624B\u5143\u306B\u53CD\u6620\u6E08\u307F\u3068\u3057\u3066\u7D9A\u3051\u308B": "A. Continue as if the pull was applied locally",
|
|
105
|
+
"B. \u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u3082\u306E\u3068\u3057\u3066\u7D9A\u3051\u308B": "B. Continue as if it was not applied",
|
|
106
|
+
"\u3069\u3061\u3089\u3068\u3082\u6C7A\u3081\u3089\u308C\u306A\u3044\u3068\u304D\u306F\u3001\u4F55\u3082\u5B9F\u884C\u3057\u306A\u3051\u308C\u3070\u3001\u6B62\u307E\u3063\u305F\u307E\u307E\u3067\u3059 (\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3001\u305D\u306E\u307E\u307E\u7DE8\u96C6\u3067\u304D\u307E\u3059)\u3002\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u5225\u306E\u5834\u6240\u3078\u5199\u3057\u3066\u304B\u3089\u9078\u3076\u3053\u3068\u3082\u3067\u304D\u307E\u3059": "If you cannot decide, run neither and it stays stopped (the local file can still be edited). You can also copy the local file elsewhere before choosing",
|
|
107
|
+
"\u624B\u5143\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u4E21\u65B9\u306B\u3001\u9055\u3046\u4E2D\u8EAB\u306E\u8A08\u753B\u304C\u3042\u308A\u307E\u3059\u3002\u5171\u901A\u306E\u5143\u304C\u5206\u304B\u3089\u306A\u3044\u306E\u3067\u3001\u81EA\u52D5\u3067\u306F\u5408\u308F\u305B\u307E\u305B\u3093\u3002\u3069\u3061\u3089\u3092\u63A1\u308B\u304B\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002": "Both the local file and the server hold different plans. Their common origin is unknown, so they are not merged automatically. Choose a side.",
|
|
108
|
+
"\u624B\u5143\u306E\u8A08\u753B\u3092\u63A1\u308B (\u30B5\u30FC\u30D0\u30FC\u306E\u8A08\u753B\u3092\u7F6E\u304D\u63DB\u3048\u308B\u3002\u524D\u306E\u4E2D\u8EAB\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u5C65\u6B74\u306B\u6B8B\u308A\u307E\u3059):": "Keep the local plan (replaces the server plan; its previous content stays in the server history):",
|
|
109
|
+
"\u30B5\u30FC\u30D0\u30FC\u306E\u8A08\u753B\u3092\u63A1\u308B (\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u7F6E\u304D\u63DB\u3048\u308B\u3002\u524D\u306E\u4E2D\u8EAB\u306F\u3001\u540C\u3058\u30D5\u30A9\u30EB\u30C0\u306B <\u30D5\u30A1\u30A4\u30EB\u540D>.before-sync-....json \u3068\u3057\u3066\u6B8B\u3057\u307E\u3059):": "Keep the server plan (replaces the local file; its previous content is kept next to it as <file name>.before-sync-....json):",
|
|
110
|
+
"(\u4E2D\u8EAB\u3092\u8AAD\u3081\u306A\u3044\u305F\u3081\u3001\u9055\u3044\u3092\u8868\u793A\u3067\u304D\u307E\u305B\u3093)": "(the content cannot be read, so the differences cannot be shown)",
|
|
111
|
+
"\u30DC\u30C3\u30AF\u30B9\u300C{title}\u300D\u304C\u52A0\u308F\u308B": 'box "{title}" is added',
|
|
112
|
+
"\u30DC\u30C3\u30AF\u30B9\u300C{title}\u300D\u306E {fields} \u304C\u5909\u308F\u308B": 'box "{title}": {fields} change',
|
|
113
|
+
"\u30DC\u30C3\u30AF\u30B9\u300C{title}\u300D\u304C\u6D88\u3048\u308B": 'box "{title}" is removed',
|
|
114
|
+
"\u5165\u51FA\u529B\u304C {count} \u4EF6\u5909\u308F\u308B": "{count} ports change",
|
|
115
|
+
"\u7DDA\u304C {count} \u4EF6\u5909\u308F\u308B": "{count} wires change",
|
|
116
|
+
"\u8A2D\u5B9A {key} \u304C\u6D88\u3048\u308B": "setting {key} is removed",
|
|
117
|
+
"\u8A2D\u5B9A {key} \u304C\u52A0\u308F\u308B": "setting {key} is added",
|
|
118
|
+
"\u8A2D\u5B9A {key} \u304C\u5909\u308F\u308B": "setting {key} changes",
|
|
119
|
+
"\u307B\u304B {count} \u4EF6": "{count} more",
|
|
120
|
+
"\u540C\u671F\u306E\u72B6\u614B\u3092\u8AAD\u3081\u306A\u3044\u30D5\u30A9\u30EB\u30C0\u304C\u3042\u308A\u307E\u3059 (\u3053\u306E\u8A08\u753B\u306E\u7D50\u3073\u4ED8\u3051\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u6D88\u3055\u305A\u306B\u3001\u4E2D\u8EAB\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044): {list}": "There are sync state folders that cannot be read (one may be this plan's binding; do not delete them, check their content): {list}",
|
|
121
|
+
"\u540C\u671F\u306E\u72B6\u614B\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u3081\u307E\u305B\u3093\u3002\u81EA\u52D5\u3067\u306F\u76F4\u3057\u307E\u305B\u3093 (\u6D88\u3059\u3068\u3001\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u3068\u524D\u56DE\u305D\u308D\u3048\u305F\u4E2D\u8EAB\u306E\u8A18\u9332\u3092\u5931\u3044\u307E\u3059): {path} ({problem})": "The sync state file cannot be read. It is not repaired automatically (deleting it loses the unfinished operation and the record of the last synced content): {path} ({problem})",
|
|
122
|
+
"\u540C\u671F\u3092\u6B62\u3081\u307E\u3057\u305F\u3002": "Sync stopped.",
|
|
123
|
+
"\u6B62\u307E\u308B\u524D\u306B\u3001\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4\u3092\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u66F8\u304D\u307E\u3057\u305F ({count} \u56DE)\u3002": "Before stopping, the server's changes were written to the local file ({count} time(s)).",
|
|
124
|
+
"\u6B62\u307E\u308B\u524D\u306B\u3001\u624B\u5143\u306E\u5909\u66F4\u3092\u30B5\u30FC\u30D0\u30FC\u3078\u9001\u308A\u307E\u3057\u305F ({count} \u56DE)\u3002": "Before stopping, local changes were sent to the server ({count} time(s)).",
|
|
125
|
+
"\u3053\u306E\u5B9F\u884C\u3067\u306F\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3082\u30B5\u30FC\u30D0\u30FC\u3082\u5909\u3048\u3066\u3044\u307E\u305B\u3093\u3002": "This run changed neither the local file nor the server.",
|
|
126
|
+
"\u540C\u671F\u306E\u72B6\u614B\u306E\u30D5\u30A9\u30EB\u30C0\u304B\u3089\u3001\u524D\u56DE\u305D\u308D\u3048\u305F\u4E2D\u8EAB\u306E\u5199\u3057\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (\u307E\u305F\u306F\u58CA\u308C\u3066\u3044\u307E\u3059)\u3002\u81EA\u52D5\u3067\u306F\u7D9A\u3051\u3089\u308C\u307E\u305B\u3093\u3002\u72B6\u614B\u306E\u8A18\u9332\u306F\u305D\u306E\u307E\u307E\u6B8B\u3057\u3066\u6B62\u307E\u3063\u3066\u3044\u307E\u3059 (\u6D88\u3055\u306A\u3044\u3067\u304F\u3060\u3055\u3044)": "The copy of the last synced content is missing from the sync state folder (or damaged). It cannot continue automatically. The state records are left as they are (do not delete them)",
|
|
127
|
+
"\u3053\u306E\u8A08\u753B\u306F\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u8A08\u753B {bound} \u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u307E\u3059\u3002\u6307\u5B9A\u3055\u308C\u305F {requested} \u3068\u306F\u9055\u3046\u306E\u3067\u3001\u4F55\u3082\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u7D50\u3073\u4ED8\u3051\u6E08\u307F\u306E\u8A08\u753B\u3068\u540C\u671F\u3059\u308B\u306A\u3089\u3001--project \u3092\u4ED8\u3051\u305A\u306B\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "This plan is bound to the server plan {bound}. That differs from the requested {requested}, so nothing was done. To sync with the bound plan, run without --project",
|
|
128
|
+
"\u3053\u306E\u8A08\u753B\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3001\u3059\u3067\u306B\u5225\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u307E\u3059: {server} (1 \u3064\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u30012 \u3064\u306E\u30B5\u30FC\u30D0\u30FC\u3078\u306F\u7D50\u3073\u4ED8\u3051\u307E\u305B\u3093)": "This plan file is already bound to another server: {server} (one file is not bound to two servers)",
|
|
129
|
+
"boxglow sync \u304C\u77E5\u3089\u306A\u3044\u6307\u5B9A\u3067\u3059: {list}": "boxglow sync does not know these options: {list}",
|
|
130
|
+
"\u3053\u306E\u7AEF\u672B\u3067\u306F\u3001\u3059\u3067\u306B\u5E38\u6642\u306E\u540C\u671F\u304C\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u305D\u3061\u3089\u304C\u3001\u3053\u306E\u8A08\u753B\u3082\u53D7\u3051\u6301\u3061\u307E\u3059 (\u52D5\u3044\u3066\u3044\u306A\u3044\u306E\u306B\u6B8B\u3063\u3066\u3044\u308B\u3068\u304D\u306F: boxglow unlock --file {path})": "A continuous sync is already running on this machine; it covers this plan too (if none is running and the lock was left behind: boxglow unlock --file {path})",
|
|
131
|
+
"\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3002{seconds} \u79D2\u5F8C\u306B\u3084\u308A\u76F4\u3057\u307E\u3059: {message}": "Cannot reach the server. Retrying in {seconds} s: {message}",
|
|
132
|
+
"\u3053\u306E\u8A08\u753B\u306E\u540C\u671F\u3092\u6B62\u3081\u3066\u3044\u307E\u3059: {message}": "Sync of this plan is stopped: {message}",
|
|
133
|
+
"(\u4E0A\u306E\u30B3\u30DE\u30F3\u30C9\u306F\u3001\u305D\u306E\u8A08\u753B\u306E\u30D5\u30A9\u30EB\u30C0\u3067\u3001\u5225\u306E\u7AEF\u672B\u753B\u9762\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u5E38\u6642\u306E\u540C\u671F\u306F\u52D5\u304B\u3057\u305F\u307E\u307E\u3067\u69CB\u3044\u307E\u305B\u3093)": "(run the command above from that plan's folder in another terminal; the continuous sync can keep running)",
|
|
134
|
+
"\u5E38\u6642\u306E\u540C\u671F\u3092\u59CB\u3081\u307E\u3057\u305F (Ctrl+C \u3067\u7D42\u4E86)\u3002\u30B5\u30FC\u30D0\u30FC: {server}\u3001\u8A08\u753B: {count} \u4EF6": "Continuous sync started (Ctrl+C to stop). Server: {server}, plans: {count}",
|
|
135
|
+
"\u5E38\u6642\u306E\u540C\u671F\u3092\u7D42\u3048\u307E\u3057\u305F\u3002": "Continuous sync stopped.",
|
|
136
|
+
"\u307E\u3060\u9001\u3063\u3066\u3044\u306A\u3044\u5909\u66F4\u304C\u3042\u308A\u307E\u3059: {file} (boxglow sync \u3067\u9001\u308C\u307E\u3059)": "There are unsent changes: {file} (send them with boxglow sync)",
|
|
137
|
+
"\u6D88\u3048\u3066\u3044\u305F\u8A2D\u5B9A\u3092\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u307E\u3057\u305F\u3002": "The missing settings were put back into the local file.",
|
|
138
|
+
"\u6B62\u307E\u308B\u524D\u306B\u3001\u6D88\u3048\u3066\u3044\u305F\u8A2D\u5B9A\u3092\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u307E\u3057\u305F\u3002": "Before stopping, the missing settings were put back into the local file.",
|
|
139
|
+
"\u8868\u793A\u3057\u305F\u9805\u76EE\u3092\u3001\u624B\u5143\u306E\u5024\u306B\u6C7A\u3081\u308B\u306A\u3089 (\u8868\u793A\u3057\u3066\u3044\u306A\u3044\u9805\u76EE\u306F\u3001\u4ECA\u307E\u3067\u3069\u304A\u308A\u81EA\u52D5\u3067\u5408\u308F\u305B\u307E\u3059):": "To settle the fields shown on the local values (fields not shown are merged automatically as before):",
|
|
140
|
+
"\u610F\u56F3\u3057\u3066\u3044\u306A\u3044\u306A\u3089\u3001\u6B21\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u6D88\u3048\u305F\u8A2D\u5B9A\u3060\u3051\u3092\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u3066\u3001\u540C\u671F\u3092\u7D9A\u3051\u307E\u3059 (\u307B\u304B\u306E\u7DE8\u96C6\u306F\u305D\u306E\u307E\u307E\u3067\u3059):": "If it is not intended, this puts only the missing settings back into the local file and continues the sync (other edits are untouched):",
|
|
141
|
+
"\u305D\u306E\u3042\u3068\u3001\u30B5\u30FC\u30D0\u30FC\u3078\u9001\u308B\u5909\u66F4:": "Then, changes sent to the server:",
|
|
142
|
+
"\u8A18\u9332 (\u30ED\u30B0\u306A\u3069) \u3060\u3051": "records only (log etc.)",
|
|
143
|
+
"\u30B5\u30FC\u30D0\u30FC\u3078\u306F\u4F55\u3082\u9001\u308A\u307E\u305B\u3093": "Nothing is sent to the server",
|
|
144
|
+
"\u30B5\u30FC\u30D0\u30FC\u3078\u9001\u308B\u524D\u306B\u3001\u5225\u306E\u78BA\u8A8D\u304C\u5FC5\u8981\u306B\u306A\u3063\u3066\u3001\u3082\u3046\u4E00\u5EA6\u6B62\u307E\u308A\u307E\u3059 ({reason})": "Before anything is sent, another confirmation is needed and it stops again ({reason})",
|
|
145
|
+
"\u540C\u671F\u306E\u72B6\u614B\u3092\u8AAD\u3081\u306A\u3044\u30D5\u30A9\u30EB\u30C0\u304C\u3042\u308B\u305F\u3081\u3001\u3053\u306E\u8A08\u753B\u304C\u3059\u3067\u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u308B\u304B\u3092\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3002\u65B0\u3057\u3044\u7D50\u3073\u4ED8\u3051\u306F\u4F5C\u308A\u307E\u305B\u3093 (\u30D5\u30A9\u30EB\u30C0\u306F\u6D88\u3055\u305A\u306B\u3001\u4E2D\u8EAB\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044): {list}": "Some sync state folders cannot be read, so it cannot be verified whether this plan is already bound. No new binding is created (do not delete the folders; check their content): {list}",
|
|
146
|
+
"--link \u306E\u9078\u629E\u306F\u3001\u4ECA\u306E\u72B6\u614B\u306B\u306F\u5F53\u3066\u306F\u307E\u308A\u307E\u305B\u3093 (\u540C\u671F\u306E\u5BFE\u8C61\u304C\u3001\u9078\u629E\u3092\u8868\u793A\u3057\u305F\u3068\u304D\u3068\u9055\u3046\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059)\u3002\u4F55\u3082\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u8868\u793A\u3055\u308C\u305F\u30B3\u30DE\u30F3\u30C9\u3092\u3001--server\u30FB--project\u30FB--file \u3092\u4ED8\u3051\u305F\u307E\u307E\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "The --link choice does not apply to the current state (the sync target may differ from when the choice was shown). Nothing was done. Run the command as shown, keeping --server, --project and --file",
|
|
147
|
+
"--resolve \u306E\u9078\u629E\u306F\u3001\u4ECA\u306E\u72B6\u614B\u306B\u306F\u5F53\u3066\u306F\u307E\u308A\u307E\u305B\u3093 (\u7AF6\u5408\u304C\u3082\u3046\u7121\u3044\u3001\u307E\u305F\u306F\u5BFE\u8C61\u304C\u9055\u3044\u307E\u3059)\u3002\u4F55\u3082\u3057\u3066\u3044\u307E\u305B\u3093\u3002boxglow sync \u3067\u3082\u3046\u4E00\u5EA6\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044": "The --resolve choice does not apply to the current state (the conflict is gone, or the target differs). Nothing was done. Check again with boxglow sync",
|
|
148
|
+
"\u30B5\u30FC\u30D0\u30FC\u304C\u5229\u7528\u8005\u3092\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3067\u3057\u305F (\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u3044\u3001\u307E\u305F\u306F\u7121\u52B9\u3067\u3059)\u3002\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044\u3002\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u306F\u6B8B\u3057\u3066\u3042\u308A\u307E\u3059": "The server could not verify who you are (the token is missing or invalid). Check the BOXGLOW_TOKEN environment variable. The unfinished operation is kept",
|
|
149
|
+
"\u30B5\u30FC\u30D0\u30FC\u304C\u3001\u3053\u306E\u8A08\u753B\u306E\u540C\u671F\u3092\u53D7\u3051\u4ED8\u3051\u307E\u305B\u3093\u3067\u3057\u305F (\u5F85\u3063\u3066\u3082\u76F4\u308A\u307E\u305B\u3093)\u3002\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u306F\u6B8B\u3057\u3066\u3042\u308A\u307E\u3059: {status} {detail}": "The server refused to sync this plan (waiting will not help). The unfinished operation is kept: {status} {detail}",
|
|
150
|
+
"\u8A08\u753B\u304C\u5927\u304D\u3059\u304E\u307E\u3059": "the plan is too large",
|
|
151
|
+
"\u4FDD\u5B58\u306E\u4E0A\u9650 (\u8A08\u753B\u306E\u6570\u3001\u307E\u305F\u306F\u4FDD\u5B58\u91CF) \u306B\u5C4A\u3044\u3066\u3044\u307E\u3059\u3002\u5C65\u6B74\u306E\u6574\u7406\u304B\u3001\u4F7F\u3063\u3066\u3044\u306A\u3044\u8A08\u753B\u306E\u524A\u9664\u304C\u8981\u308A\u307E\u3059": "the storage limit (number of plans, or stored size) is reached; the history needs tidying, or unused plans need deleting",
|
|
152
|
+
"\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u76F4\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6 boxglow sync \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u65AD\u3089\u308C\u305F\u9001\u4FE1\u306F\u9001\u308A\u76F4\u3055\u305A\u3001\u30B5\u30FC\u30D0\u30FC\u3067\u53D7\u7406\u6E08\u307F\u304B\u30FB\u53D6\u308A\u6D88\u3059\u304B\u3092\u78BA\u5B9A\u3055\u305B\u3066\u304B\u3089\u3001\u4ECA\u306E\u624B\u5143\u306E\u5185\u5BB9\u3067\u9001\u308A\u76F4\u3057\u307E\u3059": "Fix the local file, then run boxglow sync again. The refused upload is not sent again: sync first has the server settle it (already accepted, or cancelled for good) and then uploads the current local content",
|
|
153
|
+
"\u3053\u306E\u8A08\u753B\u306E\u7D50\u3073\u4ED8\u3051\u306F\u3001\u5225\u306E\u5229\u7528\u8005 ({bound}) \u306E\u3082\u306E\u3067\u3059\u3002\u4ECA\u306E\u30C8\u30FC\u30AF\u30F3\u306E\u5229\u7528\u8005\u306F {actual} \u3067\u3059\u3002\u5225\u306E\u5229\u7528\u8005\u306E\u8A08\u753B\u3092\u7F6E\u304D\u63DB\u3048\u306A\u3044\u3088\u3046\u3001\u4F55\u3082\u9001\u3089\u305A\u3001\u4F55\u3082\u66F8\u3044\u3066\u3044\u307E\u305B\u3093\u3002\u7D50\u3073\u4ED8\u3051\u305F\u3068\u304D\u306E\u5229\u7528\u8005\u306E\u30C8\u30FC\u30AF\u30F3\u3067\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "This plan is bound under another account ({bound}). The current token belongs to {actual}. Nothing was sent or written, so that another account's plan is not replaced. Run it with the token of the account it was bound with",
|
|
154
|
+
"\u3053\u306E\u8A08\u753B\u306E\u7D50\u3073\u4ED8\u3051\u306B\u306F\u3001\u5229\u7528\u8005\u306E\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093 (\u8A18\u9332\u3059\u308B\u3088\u3046\u306B\u306A\u308B\u524D\u306B\u7D50\u3073\u4ED8\u3051\u305F\u3082\u306E\u3067\u3059)\u3002\u4ECA\u306E\u30C8\u30FC\u30AF\u30F3\u306E\u5229\u7528\u8005\u306F {account} \u3067\u3059\u3002\u3053\u306E\u5229\u7528\u8005\u3067\u7D50\u3073\u4ED8\u3051\u305F\u3082\u306E\u306A\u3089\u3001\u6B21\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u5229\u7528\u8005\u3092\u8A18\u9332\u3057\u3066\u540C\u671F\u3092\u7D9A\u3051\u307E\u3059:": "This plan's binding has no account recorded (it was bound before accounts were recorded). The current token belongs to {account}. If it was bound with this account, run the following to record the account and continue:",
|
|
155
|
+
"\u5225\u306E\u5229\u7528\u8005\u3067\u7D50\u3073\u4ED8\u3051\u305F\u3082\u306E\u306A\u3089\u3001\u5B9F\u884C\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044 (\u305D\u306E\u3068\u304D\u306E\u30C8\u30FC\u30AF\u30F3\u306B\u76F4\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044)": "If it was bound with another account, do not run it (switch to that account's token and check again)",
|
|
156
|
+
"\u3053\u306E\u7248\u306E Boxglow \u306F\u53E4\u304F\u3001\u30B5\u30FC\u30D0\u30FC\u304C\u53D7\u3051\u4ED8\u3051\u307E\u305B\u3093\u3002\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044": "this Boxglow is too old for the server; update it",
|
|
157
|
+
"\u30B5\u30FC\u30D0\u30FC\u304C\u3001\u8A08\u753B\u3068\u3057\u3066\u6B63\u3057\u304F\u306A\u3044\u3068\u5224\u65AD\u3057\u307E\u3057\u305F": "the server found the plan invalid",
|
|
158
|
+
"\u8981\u6C42\u306E\u5F62\u304C\u5408\u3044\u307E\u305B\u3093": "the request was malformed",
|
|
159
|
+
"\u30B5\u30FC\u30D0\u30FC\u304C\u5229\u7528\u8005\u3092\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093 (\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u3044\u3001\u307E\u305F\u306F\u7121\u52B9\u3067\u3059)\u3002\u5E38\u6642\u306E\u540C\u671F\u3092\u6B62\u3081\u305A\u306B\u5F85\u3061\u307E\u3059\u3002\u30C8\u30FC\u30AF\u30F3\u3092\u76F4\u3057\u3066\u304B\u3089\u3001\u8D77\u52D5\u3057\u76F4\u3057\u3066\u304F\u3060\u3055\u3044": "The server cannot verify who you are (the token is missing or invalid). Continuous sync keeps waiting; fix the token and start it again",
|
|
160
|
+
"\u30B5\u30FC\u30D0\u30FC\u306E\u5834\u6240\u304C URL \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093: {server}": "The server location is not a URL: {server}",
|
|
161
|
+
"https \u3067\u306F\u306A\u3044\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u307E\u305B\u3093 (\u624B\u5143\u306E localhost \u3092\u9664\u304F): {server}": "Refusing to sign in to a server that is not https (except localhost): {server}",
|
|
162
|
+
"\u30B5\u30A4\u30F3\u30A4\u30F3\u306E\u8981\u6C42\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u5C11\u3057\u5F85\u3063\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "Too many sign-in requests. Wait a little and run it again",
|
|
163
|
+
"\u30B5\u30FC\u30D0\u30FC\u306F\u4FDD\u5B88\u4E2D\u3067\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "The server is under maintenance. Try again later",
|
|
164
|
+
"\u30B5\u30A4\u30F3\u30A4\u30F3\u3092\u59CB\u3081\u3089\u308C\u307E\u305B\u3093\u3067\u3057\u305F (\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54: {status})": "Could not start signing in (server response: {status})",
|
|
165
|
+
"\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: {message}": "Could not reach the server: {message}",
|
|
166
|
+
"\u30D6\u30E9\u30A6\u30B6\u3067\u6B21\u306E\u30DA\u30FC\u30B8\u3092\u958B\u304D\u3001\u30B3\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 (GitHub \u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u8A31\u53EF\u3057\u307E\u3059):": "Open this page in your browser and enter the code (you approve with your GitHub account):",
|
|
167
|
+
" \u30B3\u30FC\u30C9: {code}": " Code: {code}",
|
|
168
|
+
"\u8A31\u53EF\u3055\u308C\u308B\u306E\u3092\u5F85\u3063\u3066\u3044\u307E\u3059... (\u4E2D\u6B62\u306F Ctrl+C)": "Waiting for approval... (Ctrl+C to cancel)",
|
|
169
|
+
"\u30B3\u30FC\u30C9\u306E\u671F\u9650\u304C\u5207\u308C\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6 boxglow login \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "The code expired. Run boxglow login again",
|
|
170
|
+
"\u3053\u306E GitHub \u306E\u30A2\u30AB\u30A6\u30F3\u30C8 ({login}) \u306F\u3001\u307E\u3060\u62DB\u5F85\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u540C\u671F\u306F\u3001\u62DB\u5F85\u3057\u305F\u5229\u7528\u8005\u3060\u3051\u304C\u4F7F\u3048\u307E\u3059": "This GitHub account ({login}) has not been invited yet. Sync is open to invited users only",
|
|
171
|
+
"\u9806\u756A\u5F85\u3061\u306E\u767B\u9332: {url}": "Join the waitlist: {url}",
|
|
172
|
+
"\u3053\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u306F\u3001\u6B62\u3081\u3089\u308C\u3066\u3044\u307E\u3059": "This account is suspended",
|
|
173
|
+
"\u30B5\u30A4\u30F3\u30A4\u30F3\u6E08\u307F\u306E\u7AEF\u672B\u304C\u4E0A\u9650\u306B\u5C4A\u3044\u3066\u3044\u307E\u3059\u3002\u4F7F\u3063\u3066\u3044\u306A\u3044\u7AEF\u672B\u3067 boxglow logout \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u8A66\u3057\u3066\u304F\u3060\u3055\u3044": "Too many signed-in devices. Run boxglow logout on a device you no longer use, then try again",
|
|
174
|
+
"\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F (\u30D6\u30E9\u30A6\u30B6\u3067\u62D2\u5426\u3055\u308C\u307E\u3057\u305F)": "Not approved (denied in the browser)",
|
|
175
|
+
"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F (\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54: {status} {code})": "Could not sign in (server response: {status} {code})",
|
|
176
|
+
"\u3053\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u3001\u3059\u3067\u306B {current} \u3068\u3057\u3066\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u3059\u3002{next} \u306B\u66FF\u3048\u308B\u306B\u306F\u3001\u5148\u306B boxglow logout \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "Already signed in to this server as {current}. To switch to {next}, run boxglow logout first",
|
|
177
|
+
"(\u4ECA\u56DE\u767A\u884C\u3055\u308C\u305F\u30C8\u30FC\u30AF\u30F3\u3092\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u4F7F\u308F\u308C\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u304C\u6B8B\u3063\u3066\u3044\u307E\u3059)": "(The token issued just now could not be revoked on the server; an unused token remains)",
|
|
178
|
+
"\u8CC7\u683C\u60C5\u5831\u3092\u3001\u5B89\u5168\u306B\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: {reason}": "Could not store the credentials safely: {reason}",
|
|
179
|
+
"\u4ECA\u307E\u3067\u306E\u8CC7\u683C\u60C5\u5831\u3092\u3001\u5143\u306E\u5834\u6240\u306B\u623B\u305B\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u63A7\u3048\u306F\u3053\u3053\u306B\u3042\u308A\u307E\u3059: {path}": "The previous credentials could not be put back. A backup is here: {path}",
|
|
180
|
+
"\u4ECA\u56DE\u767A\u884C\u3055\u308C\u305F\u30C8\u30FC\u30AF\u30F3\u306F\u3001\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F\u3002": "The token issued just now was revoked.",
|
|
181
|
+
"\u4ECA\u56DE\u767A\u884C\u3055\u308C\u305F\u30C8\u30FC\u30AF\u30F3\u3092\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3067\u3057\u305F (\u307E\u3060\u6709\u52B9\u304B\u3082\u3057\u308C\u307E\u305B\u3093)\u3002": "The token issued just now could not be revoked on the server (it may still be valid).",
|
|
182
|
+
"\u4FDD\u5B58\u305B\u305A\u306B\u4F7F\u3046\u306B\u306F\u3001\u30C8\u30FC\u30AF\u30F3\u3092\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u3067\u6E21\u3057\u3066\u304F\u3060\u3055\u3044": "To use sync without storing credentials, pass a token in the BOXGLOW_TOKEN environment variable",
|
|
183
|
+
"\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u307E\u3057\u305F: {login} ({account})": "Signed in: {login} ({account})",
|
|
184
|
+
"\u6CE8\u610F: \u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u540C\u671F\u3067\u306F\u3001\u4FDD\u5B58\u3057\u305F\u30B5\u30A4\u30F3\u30A4\u30F3\u3088\u308A\u3001\u305D\u3061\u3089\u304C\u512A\u5148\u3055\u308C\u307E\u3059": "Note: BOXGLOW_TOKEN is set. Sync uses it in preference to the stored sign-in",
|
|
185
|
+
"\u307B\u304B\u306E boxglow login / logout \u304C\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u7D42\u308F\u3063\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "Another boxglow login / logout is running. Run it again when it has finished",
|
|
186
|
+
"\u8CC7\u683C\u60C5\u5831\u3092\u3001\u5B89\u5168\u306B\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093: {reason}": "Cannot store credentials safely: {reason}",
|
|
187
|
+
"\u3053\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u305B\u3093": "Not signed in to this server",
|
|
188
|
+
"\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F (\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u30C8\u30FC\u30AF\u30F3\u3082\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F)": "Signed out (the token was revoked on the server too)",
|
|
189
|
+
"\u624B\u5143\u306E\u8CC7\u683C\u60C5\u5831\u3092\u6D88\u3057\u307E\u3057\u305F\u3002\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u306A\u304B\u3063\u305F\u306E\u3067\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u30C8\u30FC\u30AF\u30F3\u306F\u53D6\u308A\u6D88\u305B\u3066\u3044\u307E\u305B\u3093 (\u671F\u9650\u307E\u3067\u6709\u52B9\u3067\u3059)": "Removed the local credentials. The server could not be reached, so the token was not revoked there (it stays valid until it expires)",
|
|
190
|
+
"\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u306F\u3001\u3053\u306E\u64CD\u4F5C\u3067\u306F\u53D6\u308A\u6D88\u3055\u308C\u307E\u305B\u3093 (\u540C\u671F\u3067\u306F\u3001\u5F15\u304D\u7D9A\u304D\u305D\u306E\u30C8\u30FC\u30AF\u30F3\u304C\u4F7F\u308F\u308C\u307E\u3059)": "BOXGLOW_TOKEN is not revoked by this command (sync keeps using that token)",
|
|
191
|
+
"\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u305B\u3093\u3002boxglow login --server {server} \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044": "Not signed in. Run boxglow login --server {server}",
|
|
192
|
+
"\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54: {status}": "Server response: {status}",
|
|
193
|
+
"\u5229\u7528\u8005: {login} ({account})": "Account: {login} ({account})",
|
|
194
|
+
"\u30C8\u30FC\u30AF\u30F3\u306E\u51FA\u3069\u3053\u308D: {source}": "Token source: {source}",
|
|
195
|
+
"\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN": "the BOXGLOW_TOKEN environment variable",
|
|
196
|
+
"\u4FDD\u5B58\u6E08\u307F\u306E\u30B5\u30A4\u30F3\u30A4\u30F3": "the stored sign-in",
|
|
197
|
+
"\u8A08\u753B: {count} \u500B{limit}": "Plans: {count}{limit}",
|
|
198
|
+
"\u4FDD\u5B58\u91CF: {used}{limit}": "Storage: {used}{limit}",
|
|
199
|
+
"(\u3053\u306E\u7AEF\u672B)": "(this device)",
|
|
200
|
+
"\u3053\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u306F\u3001\u540C\u671F\u3092\u4F7F\u3048\u307E\u305B\u3093 (\u6B62\u3081\u3089\u308C\u3066\u3044\u308B\u3001\u307E\u305F\u306F\u62DB\u5F85\u3055\u308C\u3066\u3044\u307E\u305B\u3093)": "This account cannot use sync (suspended, or not invited)",
|
|
201
|
+
"\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u306E\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u52B9\u3067\u3059\u3002\u76F4\u3059\u304B\u3001\u5916\u3057\u3066\u304F\u3060\u3055\u3044 (\u5916\u3059\u3068\u3001\u4FDD\u5B58\u6E08\u307F\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u3092\u4F7F\u3044\u307E\u3059)": "The token in BOXGLOW_TOKEN is not valid. Fix it or unset it (without it, the stored sign-in is used)",
|
|
202
|
+
"\u4FDD\u5B58\u6E08\u307F\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304C\u7121\u52B9\u306B\u306A\u3063\u3066\u3044\u307E\u3059 (\u671F\u9650\u5207\u308C\u30FB\u53D6\u308A\u6D88\u3057)\u3002boxglow login \u3092\u3084\u308A\u76F4\u3057\u3066\u304F\u3060\u3055\u3044": "The stored sign-in is no longer valid (expired or revoked). Run boxglow login again",
|
|
203
|
+
"\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u305B\u3093\u3002boxglow login --server <URL> \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044 (\u307E\u305F\u306F\u3001\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN)": "Not signed in. Run boxglow login --server <URL> (or set BOXGLOW_TOKEN)",
|
|
204
|
+
"\u30B5\u30FC\u30D0\u30FC\u304C\u5229\u7528\u8005\u3092\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u306F\u6B8B\u3057\u3066\u3042\u308A\u307E\u3059": "The server could not verify who you are. The unfinished operation is kept",
|
|
205
|
+
"boxglow {cmd} \u304C\u77E5\u3089\u306A\u3044\u6307\u5B9A\u3067\u3059: {list}": "Unknown option for boxglow {cmd}: {list}",
|
|
206
|
+
"\u30B5\u30FC\u30D0\u30FC\u304C\u6C7A\u307E\u3063\u3066\u3044\u307E\u305B\u3093\u3002--server <URL> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044": "No server given. Use --server <URL>",
|
|
207
|
+
"\u4FDD\u5B58\u6E08\u307F\u306E\u8CC7\u683C\u60C5\u5831\u304C\u3042\u308A\u307E\u3059\u304C\u3001\u5B89\u5168\u306B\u8AAD\u3081\u307E\u305B\u3093: {reason}": "There are stored credentials, but they cannot be read safely: {reason}",
|
|
208
|
+
"\u8AB0\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304B\u3092\u78BA\u304B\u3081\u3089\u308C\u306A\u3044\u306E\u3067\u3001\u7F6E\u304D\u63DB\u3048\u307E\u305B\u3093\u3002\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u304B\u3081\u3066\u3001\u76F4\u3059\u304B\u3001\u6D88\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044: {path}": "It is not possible to tell whose sign-in this is, so it will not be replaced. Check the file, fix or delete it, then run again: {path}",
|
|
209
|
+
"\u8CC7\u683C\u60C5\u5831\u306E\u7F6E\u304D\u5834\u306B\u3001\u4ECA\u56DE\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u6B8B\u3063\u3066\u3044\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044": "The file written just now may remain in the credentials folder. Please check",
|
|
210
|
+
"\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u306F\u3001\u3067\u304D\u3066\u3044\u307E\u305B\u3093 (\u30D5\u30A1\u30A4\u30EB\u304C\u6B8B\u3063\u3066\u3044\u3066\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u30C8\u30FC\u30AF\u30F3\u3082\u6709\u52B9\u306A\u307E\u307E\u304B\u3082\u3057\u308C\u307E\u305B\u3093)\u3002\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044: {path}": "Not signed out (the file remains and the token may still be valid on the server). Check the file: {path}",
|
|
211
|
+
"\u8CC7\u683C\u60C5\u5831\u306E\u7F6E\u304D\u5834\u3092\u3001\u5B89\u5168\u306B\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093: {reason}": "The credentials folder cannot be verified as private: {reason}",
|
|
212
|
+
"\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u306F\u3001\u3067\u304D\u3066\u3044\u307E\u305B\u3093 (\u8CC7\u683C\u60C5\u5831\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u6B8B\u3063\u3066\u3044\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093)": "Not signed out (a credentials file may remain)"
|
|
213
|
+
};
|
|
214
|
+
|
|
60
215
|
// src/i18n/en/canvasReview.ts
|
|
61
216
|
var canvasReview = {
|
|
62
217
|
"\u30AD\u30E3\u30F3\u30BB\u30EB": "Cancel",
|
|
@@ -143,7 +298,31 @@ var gui = {
|
|
|
143
298
|
"\u4FDD\u5B58\u65B9\u5F0F": "Save protocol",
|
|
144
299
|
"\u63A5\u7D9A\u5148": "Peer",
|
|
145
300
|
"\u63A5\u7D9A\u5148\u3068\u753B\u9762\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u304C\u7570\u306A\u308B\u304B\u3001\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002CLI\u30FB\u62E1\u5F35\u3092\u66F4\u65B0\u3057\u3001\u753B\u9762\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002": "The connected version differs or is unavailable. Update the CLI or extension and reload the view.",
|
|
146
|
-
"{file} \u306B\u81EA\u52D5\u3067\u4FDD\u5B58\u3057\u307E\u3059 (\u5909\u66F4\u5F8C)\u3002Save \u3067\u4ECA\u3059\u3050\u66F8\u304D\u307E\u3059": "Edits are saved automatically to {file}. Save writes immediately."
|
|
301
|
+
"{file} \u306B\u81EA\u52D5\u3067\u4FDD\u5B58\u3057\u307E\u3059 (\u5909\u66F4\u5F8C)\u3002Save \u3067\u4ECA\u3059\u3050\u66F8\u304D\u307E\u3059": "Edits are saved automatically to {file}. Save writes immediately.",
|
|
302
|
+
"\u6700\u65B0\u3067\u3059 (\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093)": "Up to date (nothing has changed)",
|
|
303
|
+
"\u6700\u65B0\u306E\u5185\u5BB9\u3092\u8AAD\u307F\u8FBC\u3080": "Load the latest content",
|
|
304
|
+
"\u3053\u306E\u30D6\u30E9\u30A6\u30B6\u306E\u4FDD\u5B58\u5148\u306B\u3001\u8A08\u753B\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093": "The plan was not found in this browser's storage",
|
|
305
|
+
"\u6700\u65B0\u306E\u5185\u5BB9\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F": "Loaded the latest content",
|
|
306
|
+
"\u6700\u65B0\u306E\u5185\u5BB9\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F: {error}": "Could not load the latest content: {error}",
|
|
307
|
+
"\u6700\u65B0\u306E\u5185\u5BB9\u3092\u8AAD\u307F\u8FBC\u3080 (\u30D5\u30A1\u30A4\u30EB\u3092\u4ECA\u3059\u3050\u8AAD\u307F\u76F4\u3059)": "Load the latest content (reread the file now)",
|
|
308
|
+
"VS Code \u304B\u3089\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093": "No response from VS Code",
|
|
309
|
+
"\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u307F\u8FBC\u3093\u3067\u3044\u307E\u3059\u2026": "Loading the file\u2026",
|
|
310
|
+
"\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3001\u307E\u3060\u7A7A\u3067\u3059": "This file is still empty",
|
|
311
|
+
"\u8A08\u753B\u306E\u540D\u524D\u3092\u5165\u529B\u3059\u308B\u3068\u3001\u3053\u306E\u30D5\u30A1\u30A4\u30EB ({file}) \u306B\u8A08\u753B\u3092\u4F5C\u308A\u307E\u3059\u3002": "Enter a name to create a plan in this file ({file}).",
|
|
312
|
+
"\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3001Boxglow \u306E\u8A08\u753B\u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093": "This file cannot be read as a Boxglow plan",
|
|
313
|
+
"\u30C6\u30AD\u30B9\u30C8\u30A8\u30C7\u30A3\u30BF\u3067\u958B\u3044\u3066\u3001\u4E2D\u8EAB\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044 (\u30D5\u30A1\u30A4\u30EB\u3092\u53F3\u30AF\u30EA\u30C3\u30AF \u2192 Open With \u2192 Text Editor)\u3002\u65B0\u3057\u304F\u59CB\u3081\u308B\u306A\u3089\u3001\u4E2D\u8EAB\u3092\u7A7A\u306B\u3057\u3066\u304B\u3089\u958B\u304D\u76F4\u3059\u3068\u3001\u540D\u524D\u3092\u4ED8\u3051\u3066\u8A08\u753B\u3092\u4F5C\u308C\u307E\u3059\u3002": "Open it in the text editor and check its content (right-click the file \u2192 Open With \u2192 Text Editor). To start fresh, empty the file and reopen it; you can then name and create a plan.",
|
|
314
|
+
"\u3082\u3046\u4E00\u5EA6\u8AAD\u307F\u8FBC\u3080": "Load again",
|
|
315
|
+
"VS Code \u304B\u3089\u3001\u30D5\u30A1\u30A4\u30EB\u306E\u4E2D\u8EAB\u3092\u53D7\u3051\u53D6\u308C\u3066\u3044\u307E\u305B\u3093": "The file content has not arrived from VS Code",
|
|
316
|
+
"\u62E1\u5F35\u3092\u6700\u65B0\u306B\u3057\u3066\u3001\u7A93\u3092\u8AAD\u307F\u8FBC\u307F\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002WSL \u306E\u4E2D\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3001WSL \u306E\u7A93 (\u5DE6\u4E0B\u306B\u300CWSL: ...\u300D\u3068\u51FA\u308B\u7A93) \u3067\u958B\u3044\u3066\u304F\u3060\u3055\u3044\u3002": 'Update the extension and reload the window. Open a file inside WSL in a WSL window (the one showing "WSL: ..." at the bottom left).',
|
|
317
|
+
"\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F": "Open a file",
|
|
318
|
+
"\u624B\u5143\u306E boxglow.json \u3092\u958B\u304D\u307E\u3059\u3002": "Open a boxglow.json on your machine.",
|
|
319
|
+
"\u958B\u3044\u3066\u898B\u308B": "Open to view",
|
|
320
|
+
"\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u3044\u3066\u3001\u56F3\u3068\u3057\u3066\u898B\u307E\u3059 (\u95B2\u89A7\u5C02\u7528\u3002\u30D5\u30A1\u30A4\u30EB\u304C\u5909\u308F\u308B\u3068\u3001\u753B\u9762\u3082\u5909\u308F\u308A\u307E\u3059)": "Open the file as a diagram (read-only; the view follows the file)",
|
|
321
|
+
"\u30B3\u30D4\u30FC\u3057\u3066\u7DE8\u96C6": "Copy and edit",
|
|
322
|
+
"\u30D5\u30A1\u30A4\u30EB\u306E\u4E2D\u8EAB\u3092\u3001\u3053\u306E\u30D6\u30E9\u30A6\u30B6\u306B\u30B3\u30D4\u30FC\u3057\u3066\u7DE8\u96C6\u3057\u307E\u3059 (\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u5909\u308F\u308A\u307E\u305B\u3093)": "Copy the file's content into this browser and edit it (the original file is not changed)",
|
|
323
|
+
"\u7DE8\u96C6\u3057\u3066\u30D5\u30A1\u30A4\u30EB\u306B\u4FDD\u5B58\u3059\u308B\u306B\u306F\u3001\u8A08\u753B\u306E\u3042\u308B\u30D5\u30A9\u30EB\u30C0\u30FC\u3067\u5B9F\u884C\u3057\u307E\u3059 (AI \u3068\u4E00\u7DD2\u306B\u4F7F\u3046\u3068\u304D\u3082\u3001\u3053\u308C\u3067\u3059)\u3002VS Code \u306E\u62E1\u5F35\u3067\u3082\u958B\u3051\u307E\u3059\u3002": "To edit and save to the file, run this in the folder containing your plan (also the way to work with an AI agent). The VS Code extension opens it too.",
|
|
324
|
+
"\u65B0\u3057\u3044\u8A08\u753B\u3092\u4F5C\u308B": "Create a new plan",
|
|
325
|
+
"\u4F5C\u3063\u305F\u8A08\u753B\u3068\u3001\u30B3\u30D4\u30FC\u3057\u305F\u8A08\u753B\u306F\u3001\u3053\u306E\u30D6\u30E9\u30A6\u30B6\u306B\u4FDD\u5B58\u3055\u308C\u307E\u3059\u3002\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3084 AI \u3068\u306F\u3001\u81EA\u52D5\u3067\u306F\u540C\u671F\u3057\u307E\u305B\u3093\u3002": "Plans you create or copy are kept in this browser. They do not automatically sync with the original file or your AI agent."
|
|
147
326
|
};
|
|
148
327
|
|
|
149
328
|
// src/i18n/en/common.ts
|
|
@@ -181,6 +360,13 @@ var common = {
|
|
|
181
360
|
|
|
182
361
|
// src/i18n/en/inspector.ts
|
|
183
362
|
var inspector = {
|
|
363
|
+
"\u65E5\u7A0B": "Dates",
|
|
364
|
+
"\u305D\u306E\u4ED6": "More",
|
|
365
|
+
"\u914D\u7DDA": "Wire",
|
|
366
|
+
"\u5165\u51FA\u529B": "I/O",
|
|
367
|
+
"\u63A5\u7D9A\u3092\u5916\u3059": "Disconnect",
|
|
368
|
+
"\u4F9B\u7D66\u5143\u306E\u5B8C\u4E86\u30FB\u6210\u679C\u7269\u3001\u307E\u305F\u306F\u4E0A\u6D41\u306E\u5165\u529B\u3092\u78BA\u8A8D\u3067\u304D\u3066\u3044\u307E\u3059\u3002": "The source is complete, has an artifact, or has a confirmed upstream input.",
|
|
369
|
+
"\u4F9B\u7D66\u5143\u304C\u672A\u5B8C\u4E86\u3067\u6210\u679C\u7269\u304C\u306A\u3044\u3001\u307E\u305F\u306F\u4E0A\u6D41\u306E\u5165\u529B\u304C\u307E\u3060\u78BA\u5B9A\u3057\u3066\u3044\u307E\u305B\u3093\u3002": "The source is incomplete without an artifact, or its upstream input is not yet confirmed.",
|
|
184
370
|
// パネル共通
|
|
185
371
|
// プロジェクト設定
|
|
186
372
|
"\u30B4\u30FC\u30EB (\u4F55\u3092\u9054\u6210\u3057\u305F\u3044\u304B)": "Goal (what to achieve)",
|
|
@@ -243,6 +429,7 @@ var inspector = {
|
|
|
243
429
|
|
|
244
430
|
// src/i18n/en/parts.ts
|
|
245
431
|
var parts = {
|
|
432
|
+
"\u56DE\u7B54\u3059\u308B": "Answer",
|
|
246
433
|
// ---- parts.tsx: 成果物の一覧 ----
|
|
247
434
|
"\u30D5\u30A1\u30A4\u30EB\u306E\u79FB\u52D5\u3092\u691C\u51FA\u3057\u3001\u30D1\u30B9\u3092\u4ED8\u3051\u66FF\u3048\u307E\u3057\u305F": "File move detected; path updated",
|
|
248
435
|
"\u73FE\u5728\u306E\u30EA\u30DD\u30B8\u30C8\u30EA\u306B\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (\u30B3\u30DF\u30C3\u30C8\u304B\u3089\u306F\u53D6\u308A\u51FA\u305B\u307E\u3059)": "Not found in the current repository (still available from the commit)",
|
|
@@ -333,6 +520,9 @@ var parts = {
|
|
|
333
520
|
|
|
334
521
|
// src/i18n/en/canvas.ts
|
|
335
522
|
var canvas = {
|
|
523
|
+
"\u5224\u65AD\u5F85\u3061 {n}": "Decisions {n}",
|
|
524
|
+
"\u4FEF\u77B0\u8868\u793A": "Overview",
|
|
525
|
+
"\u901A\u5E38\u8868\u793A": "Standard",
|
|
336
526
|
// ボックス (BlockNode): 題名の行
|
|
337
527
|
"\u30AB\u30C6\u30B4\u30EA: {label}": "Category: {label}",
|
|
338
528
|
"{title} (\u90E8\u54C1: {name})": "{title} (part: {name})",
|
|
@@ -503,6 +693,7 @@ var cli = {
|
|
|
503
693
|
"\u6574\u5217: \u300C{title}\u300D\u306E\u4E2D": 'Layout: inside "{title}"',
|
|
504
694
|
"\u6574\u5217: \u5168\u4F53": "Layout: everything",
|
|
505
695
|
"merge <base> <ours> <theirs> \u306E 3 \u3064\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044": "Specify all three: merge <base> <ours> <theirs>",
|
|
696
|
+
"\u30DE\u30FC\u30B8\u3057\u305F\u7D50\u679C\u304C\u8A08\u753B\u3068\u3057\u3066\u6B63\u3057\u304F\u306A\u3044\u305F\u3081\u3001\u66F8\u304D\u8FBC\u307F\u307E\u305B\u3093\u3067\u3057\u305F (\u81EA\u5206\u306E\u5074\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u305D\u306E\u307E\u307E\u3067\u3059): {problem}": "The merged result is not a valid plan, so nothing was written (your side of the file is unchanged): {problem}",
|
|
506
697
|
"ours / theirs \u304C\u8AAD\u3081\u307E\u305B\u3093": "Cannot read ours / theirs",
|
|
507
698
|
"\u30DE\u30FC\u30B8\u3067\u4E21\u5074\u304C\u5909\u66F4: {path} (\u63A1\u7528: {ours} / \u76F8\u624B: {theirs})": "Changed on both sides in merge: {path} (kept: {ours} / theirs: {theirs})",
|
|
508
699
|
"\u30DE\u30FC\u30B8: \u76F8\u624B\u306E\u5909\u66F4 {count} \u4EF6\u3092\u53D6\u308A\u8FBC\u307F": "Merge: took {count} change(s) from theirs",
|
|
@@ -616,6 +807,7 @@ var cli = {
|
|
|
616
807
|
"Boxglow \u306E\u5370 (boxglow:begin / boxglow:end) \u304C\u6B63\u3057\u304F\u5BFE\u306B\u306A\u3063\u3066\u3044\u307E\u305B\u3093: {path}": "Invalid Boxglow markers (boxglow:begin / boxglow:end): {path}",
|
|
617
808
|
"\u968E\u5C64\u306E\u4E00\u89A7\u3092\u7701\u304F (\u5224\u65AD\u30FB\u6D3B\u52D5\u30FB\u6B21\u306E\u5019\u88DC\u306F\u6B8B\u3059)": "Omit the full hierarchy; keep decisions, activity and next actions",
|
|
618
809
|
"\u4F5C\u696D\u306E\u518D\u958B\u7528\u306E\u6982\u8981: \u5F15\u304D\u7D99\u304E\u30E1\u30E2\u30FBAI \u672A\u78BA\u8A8D\u306E\u56DE\u7B54\u30FB\u4F5C\u696D\u4E2D\u30FB\u6B21\u306E\u5019\u88DC (\u56DE\u7B54\u306F\u78BA\u8A8D\u6E08\u307F\u306B\u306A\u3089\u306A\u3044)": "Resume work: recent handoffs, unread human answers, active tasks and next candidates. Does not acknowledge answers.",
|
|
810
|
+
"\u77ED\u3044\u5F62\u3067\u8AAD\u3080 (\u5BFE\u8C61\u306E\u60C5\u5831\u306F\u5168\u90E8\u3001\u89AA\u3068\u5165\u529B\u5143\u306F\u984C\u540D\u30FB\u72B6\u614B\u30FB\u6709\u52B9\u306A\u5224\u65AD\u30FB\u5BFE\u8C61\u306B\u3064\u306A\u304C\u308B\u51FA\u529B\u3060\u3051\u3002\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u540C\u3058)": "Read the short form: everything about the target, and for ancestors and upstream boxes only title, status, decisions in effect and the outputs that feed the target. The contextToken is the same.",
|
|
619
811
|
"\u30DC\u30C3\u30AF\u30B9\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u8AAD\u3080: \u89AA\u3068\u5165\u529B\u5143\u306E\u5224\u65AD\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3002guard \u4ED8\u304D\u306E\u5909\u66F4\u306B\u4F7F\u3046 contextToken \u3092\u8FD4\u3059 (\u81EA\u5206\u306E\u64CD\u4F5C\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5909\u308F\u308B\u3068\u3001\u305D\u306E\u64CD\u4F5C\u306E\u51FA\u529B\u306B\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B)": "Read persistent task context, ancestor and upstream decisions, contracts and handoff notes. Returns contextToken for guarded changes (when your own command changes the context, its output ends with the new context token).",
|
|
620
812
|
"\u4E2D\u65AD\u3084\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B\u3001\u5206\u304B\u3063\u305F\u3053\u3068\u30FB\u6B21\u306E\u624B\u9806\u30FB\u672A\u89E3\u6C7A\u306E\u70B9\u3092\u8A08\u753B\u306B\u6B8B\u3059": "Persist findings, next steps and unresolved questions before interruption or handoff.",
|
|
621
813
|
"\u8A08\u753B\u304C\u5927\u304D\u3059\u304E\u307E\u3059 (\u4E0A\u9650 5 MiB)": "Project exceeds 5 MiB",
|
|
@@ -811,7 +1003,7 @@ var report = {
|
|
|
811
1003
|
};
|
|
812
1004
|
|
|
813
1005
|
// src/i18n/en/index.ts
|
|
814
|
-
var en = { ...common, ...inspector, ...parts, ...canvas, ...app, ...cli, ...model, ...report, ...gui, ...canvasReview, ...workflow };
|
|
1006
|
+
var en = { ...common, ...inspector, ...parts, ...canvas, ...app, ...cli, ...model, ...report, ...gui, ...canvasReview, ...workflow, ...sync };
|
|
815
1007
|
|
|
816
1008
|
// src/i18n/core.ts
|
|
817
1009
|
var STORAGE_KEY = "boxglow:lang";
|
|
@@ -848,7 +1040,7 @@ function t(ja, vars) {
|
|
|
848
1040
|
if (typeof document !== "undefined") document.documentElement.lang = current;
|
|
849
1041
|
|
|
850
1042
|
// package.json
|
|
851
|
-
var version = "0.5.
|
|
1043
|
+
var version = "0.5.2";
|
|
852
1044
|
|
|
853
1045
|
// src/model/version.ts
|
|
854
1046
|
var APP_VERSION = version;
|
|
@@ -903,7 +1095,7 @@ async function startMcp(run) {
|
|
|
903
1095
|
opt(argv2, "context-token", a.contextToken);
|
|
904
1096
|
return safe(run, argv2);
|
|
905
1097
|
});
|
|
906
|
-
server.registerTool("boxglow_context", { description: t("\u30DC\u30C3\u30AF\u30B9\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u8AAD\u3080: \u89AA\u3068\u5165\u529B\u5143\u306E\u5224\u65AD\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3002guard \u4ED8\u304D\u306E\u5909\u66F4\u306B\u4F7F\u3046 contextToken \u3092\u8FD4\u3059 (\u81EA\u5206\u306E\u64CD\u4F5C\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5909\u308F\u308B\u3068\u3001\u305D\u306E\u64CD\u4F5C\u306E\u51FA\u529B\u306B\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B)"), inputSchema: { block: block2 }, annotations: { readOnlyHint: true } }, async ({ block: b }) => safe(run, ["context", b]));
|
|
1098
|
+
server.registerTool("boxglow_context", { description: t("\u30DC\u30C3\u30AF\u30B9\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u8AAD\u3080: \u89AA\u3068\u5165\u529B\u5143\u306E\u5224\u65AD\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3002guard \u4ED8\u304D\u306E\u5909\u66F4\u306B\u4F7F\u3046 contextToken \u3092\u8FD4\u3059 (\u81EA\u5206\u306E\u64CD\u4F5C\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5909\u308F\u308B\u3068\u3001\u305D\u306E\u64CD\u4F5C\u306E\u51FA\u529B\u306B\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B)"), inputSchema: { block: block2, brief: z.boolean().optional().describe(t("\u77ED\u3044\u5F62\u3067\u8AAD\u3080 (\u5BFE\u8C61\u306E\u60C5\u5831\u306F\u5168\u90E8\u3001\u89AA\u3068\u5165\u529B\u5143\u306F\u984C\u540D\u30FB\u72B6\u614B\u30FB\u6709\u52B9\u306A\u5224\u65AD\u30FB\u5BFE\u8C61\u306B\u3064\u306A\u304C\u308B\u51FA\u529B\u3060\u3051\u3002\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u540C\u3058)")) }, annotations: { readOnlyHint: true } }, async ({ block: b, brief }) => safe(run, ["context", b, ...brief ? ["--brief"] : []]));
|
|
907
1099
|
server.registerTool("boxglow_checkpoint", { description: t("\u4E2D\u65AD\u3084\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B\u3001\u5206\u304B\u3063\u305F\u3053\u3068\u30FB\u6B21\u306E\u624B\u9806\u30FB\u672A\u89E3\u6C7A\u306E\u70B9\u3092\u8A08\u753B\u306B\u6B8B\u3059"), inputSchema: { block: block2, note: z.string(), contextToken: z.string().optional() } }, async (a) => {
|
|
908
1100
|
const argv2 = ["checkpoint", a.block, "--note", a.note];
|
|
909
1101
|
opt(argv2, "context-token", a.contextToken);
|
|
@@ -1495,9 +1687,9 @@ function checkDone(p, id, added, actor) {
|
|
|
1495
1687
|
}
|
|
1496
1688
|
function noteFreshness(b, at) {
|
|
1497
1689
|
if (!at || !b.statusChangedAt) return "unknown";
|
|
1498
|
-
const
|
|
1499
|
-
if (!Number.isFinite(
|
|
1500
|
-
return
|
|
1690
|
+
const written2 = Date.parse(at), changed = Date.parse(b.statusChangedAt);
|
|
1691
|
+
if (!Number.isFinite(written2) || !Number.isFinite(changed)) return "unknown";
|
|
1692
|
+
return written2 < changed ? "older" : "current";
|
|
1501
1693
|
}
|
|
1502
1694
|
function freshnessText(b, at, show = "always") {
|
|
1503
1695
|
const freshness = noteFreshness(b, at);
|
|
@@ -2192,6 +2384,30 @@ function toJSON(p) {
|
|
|
2192
2384
|
const { updatedAt: _u, version: _v, ...rest } = p;
|
|
2193
2385
|
return JSON.stringify(rest, null, 2);
|
|
2194
2386
|
}
|
|
2387
|
+
var KNOWN_PROJECT_KEYS = /* @__PURE__ */ new Set([
|
|
2388
|
+
"schemaVersion",
|
|
2389
|
+
"id",
|
|
2390
|
+
"name",
|
|
2391
|
+
"description",
|
|
2392
|
+
"createdAt",
|
|
2393
|
+
"visibility",
|
|
2394
|
+
"members",
|
|
2395
|
+
"blocks",
|
|
2396
|
+
"ports",
|
|
2397
|
+
"edges",
|
|
2398
|
+
"terminals",
|
|
2399
|
+
"log",
|
|
2400
|
+
"agents",
|
|
2401
|
+
"nextKey",
|
|
2402
|
+
"inputGroups",
|
|
2403
|
+
"contextGuard",
|
|
2404
|
+
"workflowPolicy",
|
|
2405
|
+
"focusBlockId",
|
|
2406
|
+
"handoffs",
|
|
2407
|
+
"lang",
|
|
2408
|
+
"updatedAt",
|
|
2409
|
+
"version"
|
|
2410
|
+
]);
|
|
2195
2411
|
function fromJSON(text2) {
|
|
2196
2412
|
let data;
|
|
2197
2413
|
try {
|
|
@@ -2226,6 +2442,9 @@ function fromJSON(text2) {
|
|
|
2226
2442
|
if (typeof d.focusBlockId === "string" && q.blocks[d.focusBlockId]) q.focusBlockId = d.focusBlockId;
|
|
2227
2443
|
if (d.handoffs && typeof d.handoffs === "object") q.handoffs = d.handoffs;
|
|
2228
2444
|
if (d.lang === "en" || d.lang === "ja") q.lang = d.lang;
|
|
2445
|
+
for (const [key, value] of Object.entries(d)) {
|
|
2446
|
+
if (!KNOWN_PROJECT_KEYS.has(key) && !(key in q)) q[key] = value;
|
|
2447
|
+
}
|
|
2229
2448
|
for (const x of Object.values(q.ports)) if (x.groupId && !q.inputGroups.some((gp) => gp.id === x.groupId)) delete x.groupId;
|
|
2230
2449
|
for (const b of Object.values(q.blocks)) {
|
|
2231
2450
|
if (b.activity === void 0) b.activity = null;
|
|
@@ -2266,7 +2485,7 @@ function wrapIntoProject(p) {
|
|
|
2266
2485
|
const minY = Math.min(...topTasks.map((b) => b.position.y));
|
|
2267
2486
|
for (const b of topTasks) {
|
|
2268
2487
|
q.blocks[b.id].parentId = pid;
|
|
2269
|
-
q.blocks[b.id].position = { x: b.position.x - minX + CHILD_PADDING.left, y: b.position.y - minY + childTop(q, pid) };
|
|
2488
|
+
q.blocks[b.id].position = { ...b.position, x: b.position.x - minX + CHILD_PADDING.left, y: b.position.y - minY + childTop(q, pid) };
|
|
2270
2489
|
}
|
|
2271
2490
|
for (const e of Object.values(q.edges)) {
|
|
2272
2491
|
const tp = q.ports[e.to.portId];
|
|
@@ -2313,13 +2532,13 @@ function wrapIntoProject(p) {
|
|
|
2313
2532
|
function appendLog(q, ev) {
|
|
2314
2533
|
q.log.push({ id: newId(), at: now(), ...ev });
|
|
2315
2534
|
if (q.log.length > LOG_LIMIT) q.log.splice(0, q.log.length - LOG_LIMIT);
|
|
2316
|
-
q.agents[ev.actor] = { lastSeen: now() };
|
|
2535
|
+
q.agents[ev.actor] = { ...q.agents[ev.actor], lastSeen: now() };
|
|
2317
2536
|
}
|
|
2318
2537
|
function setActivity(p, blockId, actor, state, note) {
|
|
2319
2538
|
const q = touch(p);
|
|
2320
2539
|
const b = q.blocks[blockId];
|
|
2321
2540
|
if (!b || blockId === ROOT_ID) return p;
|
|
2322
|
-
b.activity = { actor, state, note, since: now() };
|
|
2541
|
+
b.activity = { ...b.activity && b.activity.actor === actor ? b.activity : {}, actor, state, note, since: now() };
|
|
2323
2542
|
if (state === "working" && b.status === "black") {
|
|
2324
2543
|
b.status = "gray";
|
|
2325
2544
|
b.statusChangedAt = now();
|
|
@@ -2512,13 +2731,13 @@ function splitBlock(p, parentId, spec, actor) {
|
|
|
2512
2731
|
if ((b.outputs ?? []).length > 1) errors.push(t("\u300C{title}\u300D\u306E\u51FA\u529B\u306F 1 \u672C\u306B\u3057\u307E\u3057\u305F (\u4E0B\u306E\u968E\u5C64\u3092\u6301\u305F\u306A\u3044\u30DC\u30C3\u30AF\u30B9\u306E\u51FA\u529B\u306F 1 \u672C\u3002{omitted} \u306F\u7701\u7565)", { title: b.title, omitted: b.outputs.slice(1).join(", ") }));
|
|
2513
2732
|
for (const name of b.inputs ?? []) q = addPort(q, { blockId: r.blockId, direction: "in", name }).project;
|
|
2514
2733
|
}
|
|
2515
|
-
const
|
|
2734
|
+
const resolve6 = (ref, dir2) => {
|
|
2516
2735
|
let why = t("\u30DC\u30C3\u30AF\u30B9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
|
|
2517
2736
|
for (const { title, portName } of refSplits(ref)) {
|
|
2518
2737
|
const isParent = title === "parent" || title === parent.title || title === parentId;
|
|
2519
2738
|
const blockId = isParent ? parentId : made[title] ?? findBlock(q, title).block?.id;
|
|
2520
2739
|
if (!blockId) continue;
|
|
2521
|
-
const want = isParent ?
|
|
2740
|
+
const want = isParent ? dir2 === "out" ? "in" : "out" : dir2;
|
|
2522
2741
|
const port2 = portsOf(q, blockId, want).find((x) => x.name === portName);
|
|
2523
2742
|
if (!port2) {
|
|
2524
2743
|
why = want === "in" ? t("\u300C{title}\u300D\u306B\u5165\u529B\u300C{port}\u300D\u304C\u3042\u308A\u307E\u305B\u3093", { title, port: portName }) : t("\u300C{title}\u300D\u306B\u51FA\u529B\u300C{port}\u300D\u304C\u3042\u308A\u307E\u305B\u3093", { title, port: portName });
|
|
@@ -2529,8 +2748,8 @@ function splitBlock(p, parentId, spec, actor) {
|
|
|
2529
2748
|
return { ep: null, why };
|
|
2530
2749
|
};
|
|
2531
2750
|
for (const c of spec.connections ?? []) {
|
|
2532
|
-
const from =
|
|
2533
|
-
let to =
|
|
2751
|
+
const from = resolve6(c.from, "out");
|
|
2752
|
+
let to = resolve6(c.to, "in");
|
|
2534
2753
|
if (!to.ep && from.ep && !c.to.includes(".")) {
|
|
2535
2754
|
const tid = c.to === "parent" || c.to === parent.title || c.to === parentId ? null : made[c.to] ?? findBlock(q, c.to).block?.id;
|
|
2536
2755
|
if (tid) {
|
|
@@ -2627,20 +2846,20 @@ function instantiateNode(p, parentId, node, origin, position) {
|
|
|
2627
2846
|
made[c.title] = rc.blockId;
|
|
2628
2847
|
y += 120;
|
|
2629
2848
|
}
|
|
2630
|
-
const
|
|
2849
|
+
const resolve6 = (ref, dir2) => {
|
|
2631
2850
|
for (const { title, portName } of refSplits(ref)) {
|
|
2632
2851
|
const isParent = title === "parent";
|
|
2633
2852
|
const blockId = isParent ? id : made[title];
|
|
2634
2853
|
if (!blockId) continue;
|
|
2635
|
-
const want = isParent ?
|
|
2854
|
+
const want = isParent ? dir2 === "out" ? "in" : "out" : dir2;
|
|
2636
2855
|
const port2 = portsOf(q, blockId, want).find((x) => x.name === portName);
|
|
2637
2856
|
if (port2) return { portId: port2.id, side: isParent ? "inner" : "outer" };
|
|
2638
2857
|
}
|
|
2639
2858
|
return null;
|
|
2640
2859
|
};
|
|
2641
2860
|
for (const c of node.connections) {
|
|
2642
|
-
const from =
|
|
2643
|
-
const to =
|
|
2861
|
+
const from = resolve6(c.from, "out");
|
|
2862
|
+
const to = resolve6(c.to, "in");
|
|
2644
2863
|
if (from && to) q = connect(q, from, to).project;
|
|
2645
2864
|
}
|
|
2646
2865
|
if (q.blocks[id].status !== "gray") q.blocks[id].statusChangedAt = now();
|
|
@@ -2840,7 +3059,7 @@ function moveBlockToParent(p, blockId, newParentId, position) {
|
|
|
2840
3059
|
pending.push(mineIsFrom ? { from: myEp, to: otherEp } : { from: otherEp, to: myEp });
|
|
2841
3060
|
}
|
|
2842
3061
|
q.blocks[blockId].parentId = newParentId;
|
|
2843
|
-
q.blocks[blockId].position = { x: Math.max(CHILD_PADDING.left, position.x), y: Math.max(childTop(q, newParentId), position.y) };
|
|
3062
|
+
q.blocks[blockId].position = { ...q.blocks[blockId].position, x: Math.max(CHILD_PADDING.left, position.x), y: Math.max(childTop(q, newParentId), position.y) };
|
|
2844
3063
|
for (const pr of pending) routeConnect(q, pr.from, pr.to);
|
|
2845
3064
|
if (q.blocks[newParentId].status === "black") {
|
|
2846
3065
|
q.blocks[newParentId].status = "gray";
|
|
@@ -2908,7 +3127,7 @@ function pushOut(q, blockId, sizeOf, against, siblings) {
|
|
|
2908
3127
|
const ok = moves.filter((m) => !nested || m.x >= CHILD_PADDING.left && m.y >= childTop(q, cur.parentId));
|
|
2909
3128
|
const best = (ok.length > 0 ? ok : moves).sort((a, c) => a.d - c.d)[0];
|
|
2910
3129
|
if (!best) break;
|
|
2911
|
-
q.blocks[blockId] = { ...cur, position: { x: best.x, y: best.y } };
|
|
3130
|
+
q.blocks[blockId] = { ...cur, position: { ...cur.position, x: best.x, y: best.y } };
|
|
2912
3131
|
moved = true;
|
|
2913
3132
|
}
|
|
2914
3133
|
return moved;
|
|
@@ -3060,6 +3279,14 @@ function validateProjectText(text2) {
|
|
|
3060
3279
|
}
|
|
3061
3280
|
return fromJSON(text2);
|
|
3062
3281
|
}
|
|
3282
|
+
function projectProblem(project) {
|
|
3283
|
+
try {
|
|
3284
|
+
validateProjectText(JSON.stringify(project));
|
|
3285
|
+
return null;
|
|
3286
|
+
} catch (e) {
|
|
3287
|
+
return e instanceof Error ? e.message : String(e);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3063
3290
|
|
|
3064
3291
|
// cli/serve.ts
|
|
3065
3292
|
var MAX_BODY = 5 * 1024 * 1024;
|
|
@@ -3117,8 +3344,8 @@ function startServe(opts) {
|
|
|
3117
3344
|
log(t("[serve] \u30D5\u30A1\u30A4\u30EB\u306E\u76E3\u8996\u3092\u59CB\u3081\u3089\u308C\u307E\u305B\u3093: {message}", { message: e instanceof Error ? e.message : String(e) }));
|
|
3118
3345
|
}
|
|
3119
3346
|
const server = createServer(async (req, res) => {
|
|
3120
|
-
const send = (status, body, type = "application/json",
|
|
3121
|
-
res.writeHead(status, { "content-type": type, "cache-control": "no-store", "x-content-type-options": "nosniff", ...
|
|
3347
|
+
const send = (status, body, type = "application/json", headers3 = {}) => {
|
|
3348
|
+
res.writeHead(status, { "content-type": type, "cache-control": "no-store", "x-content-type-options": "nosniff", ...headers3 });
|
|
3122
3349
|
res.end(body);
|
|
3123
3350
|
};
|
|
3124
3351
|
try {
|
|
@@ -3220,6 +3447,16 @@ function agentContext(p, blockId) {
|
|
|
3220
3447
|
if (parent) visit(parent);
|
|
3221
3448
|
};
|
|
3222
3449
|
visit(blockId);
|
|
3450
|
+
const bundledOutputs = /* @__PURE__ */ new Map();
|
|
3451
|
+
const bundled = (portId) => {
|
|
3452
|
+
for (const e of incomingEdges(p, { portId, side: "inner" })) {
|
|
3453
|
+
const child = p.ports[e.from.portId];
|
|
3454
|
+
const owner = child && p.blocks[child.blockId];
|
|
3455
|
+
if (!child || !owner || child.direction !== "out" || bundledOutputs.has(e.id)) continue;
|
|
3456
|
+
bundledOutputs.set(e.id, { portId: child.id, into: portId, blockId: owner.id, key: owner.key, title: owner.title, name: child.name, description: child.description, artifacts: child.artifacts });
|
|
3457
|
+
bundled(child.id);
|
|
3458
|
+
}
|
|
3459
|
+
};
|
|
3223
3460
|
const upstream = (id, seen = /* @__PURE__ */ new Set()) => {
|
|
3224
3461
|
if (seen.has(id)) return;
|
|
3225
3462
|
seen.add(id);
|
|
@@ -3227,7 +3464,10 @@ function agentContext(p, blockId) {
|
|
|
3227
3464
|
for (const e of Object.values(p.edges)) {
|
|
3228
3465
|
const from = p.ports[e.from.portId];
|
|
3229
3466
|
const to = p.ports[e.to.portId];
|
|
3230
|
-
if (to?.blockId === id && to.direction === "in" && from && from.blockId !== id)
|
|
3467
|
+
if (to?.blockId === id && to.direction === "in" && from && from.blockId !== id) {
|
|
3468
|
+
upstream(from.blockId, seen);
|
|
3469
|
+
if (from.direction === "out") bundled(from.id);
|
|
3470
|
+
}
|
|
3231
3471
|
}
|
|
3232
3472
|
};
|
|
3233
3473
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -3258,9 +3498,132 @@ function agentContext(p, blockId) {
|
|
|
3258
3498
|
ports: Object.values(p.ports).filter((port2) => port2.blockId === id).sort((a, b2) => a.id.localeCompare(b2.id)).map((port2) => ({ id: port2.id, direction: port2.direction, name: port2.name, description: port2.description, required: port2.required, artifacts: port2.artifacts }))
|
|
3259
3499
|
};
|
|
3260
3500
|
}),
|
|
3501
|
+
bundledOutputs: [...bundledOutputs.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v),
|
|
3261
3502
|
connections: Object.values(p.edges).filter((e) => ids.has(p.ports[e.to.portId]?.blockId) && ids.has(p.ports[e.from.portId]?.blockId)).sort((a, b) => a.id.localeCompare(b.id))
|
|
3262
3503
|
};
|
|
3263
3504
|
}
|
|
3505
|
+
function briefContext(p, blockId) {
|
|
3506
|
+
const full = agentContext(p, blockId);
|
|
3507
|
+
const byId = new Map(full.blocks.map((b) => [b.id, b]));
|
|
3508
|
+
const target = byId.get(blockId);
|
|
3509
|
+
const nameOf = (id) => {
|
|
3510
|
+
const b = p.blocks[id];
|
|
3511
|
+
return b ? { key: b.key, title: b.title } : { key: void 0, title: id };
|
|
3512
|
+
};
|
|
3513
|
+
const split = (decisions) => ({
|
|
3514
|
+
answered: decisions.filter((d) => d.answer !== void 0).map((d) => ({ id: d.id, question: d.question, context: d.context, options: d.options, answer: d.answer })),
|
|
3515
|
+
open: decisions.filter((d) => d.answer === void 0).map((d) => ({ id: d.id, question: d.question, context: d.context, options: d.options })),
|
|
3516
|
+
history: decisions.reduce((n, d) => n + (d.history?.length ?? 0), 0)
|
|
3517
|
+
});
|
|
3518
|
+
const feeds = /* @__PURE__ */ new Map();
|
|
3519
|
+
const shownPorts = /* @__PURE__ */ new Set();
|
|
3520
|
+
const targetPorts = [...portsOf(p, blockId, "in"), ...portsOf(p, blockId, "out")];
|
|
3521
|
+
const inputs = targetPorts.filter((port2) => port2.direction === "in").map((port2) => {
|
|
3522
|
+
const sources = [];
|
|
3523
|
+
const trace = (portId, side, seen, via = []) => {
|
|
3524
|
+
for (const e of incomingEdges(p, { portId, side })) {
|
|
3525
|
+
const from = p.ports[e.from.portId];
|
|
3526
|
+
if (!from || seen.has(from.id)) continue;
|
|
3527
|
+
seen.add(from.id);
|
|
3528
|
+
const owner = p.blocks[from.blockId];
|
|
3529
|
+
const relayed = from.direction === "in" ? incomingEdges(p, { portId: from.id, side: "outer" }) : [];
|
|
3530
|
+
if (relayed.length > 0) {
|
|
3531
|
+
trace(from.id, "outer", seen, [...via, owner?.key ?? owner?.title ?? from.blockId]);
|
|
3532
|
+
continue;
|
|
3533
|
+
}
|
|
3534
|
+
const bundled = from.direction === "out" ? incomingEdges(p, { portId: from.id, side: "inner" }) : [];
|
|
3535
|
+
const sourceReady = isSourceReady(p, e.from);
|
|
3536
|
+
shownPorts.add(from.id);
|
|
3537
|
+
feeds.set(from.blockId, [...feeds.get(from.blockId) ?? [], from.name]);
|
|
3538
|
+
sources.push({
|
|
3539
|
+
...nameOf(from.blockId),
|
|
3540
|
+
output: from.name,
|
|
3541
|
+
...from.description ? { description: from.description } : {},
|
|
3542
|
+
status: owner?.status,
|
|
3543
|
+
artifacts: from.artifacts,
|
|
3544
|
+
...owner?.artifacts.length ? { otherArtifacts: owner.artifacts.length } : {},
|
|
3545
|
+
...via.length ? { via } : {},
|
|
3546
|
+
...bundled.length ? { bundles: bundled.length } : {},
|
|
3547
|
+
...bundled.length && (from.artifacts.length === 0 || !sourceReady) ? { inner: bundled.flatMap((b) => {
|
|
3548
|
+
const child = p.ports[b.from.portId];
|
|
3549
|
+
if (!child) return [];
|
|
3550
|
+
shownPorts.add(child.id);
|
|
3551
|
+
return [{ ...nameOf(child.blockId), output: child.name, status: p.blocks[child.blockId]?.status, artifacts: child.artifacts }];
|
|
3552
|
+
}) } : {}
|
|
3553
|
+
});
|
|
3554
|
+
}
|
|
3555
|
+
};
|
|
3556
|
+
trace(port2.id, "outer", /* @__PURE__ */ new Set([port2.id]));
|
|
3557
|
+
const ready = isInputReady(p, port2.id);
|
|
3558
|
+
return {
|
|
3559
|
+
name: port2.name,
|
|
3560
|
+
description: port2.description,
|
|
3561
|
+
required: port2.required,
|
|
3562
|
+
artifacts: port2.artifacts,
|
|
3563
|
+
sources,
|
|
3564
|
+
ready,
|
|
3565
|
+
missing: !!port2.required && !ready
|
|
3566
|
+
};
|
|
3567
|
+
});
|
|
3568
|
+
const outputs = targetPorts.filter((port2) => port2.direction === "out").map((port2) => ({ name: port2.name, description: port2.description, artifacts: port2.artifacts }));
|
|
3569
|
+
const mine = split(target.decisions);
|
|
3570
|
+
const parents = new Set(ancestorsOf(p, blockId).map((b) => b.id));
|
|
3571
|
+
const omitted = { descriptions: 0, ports: 0, decisionHistory: mine.history, handoffs: 0, artifacts: 0, connections: 0 };
|
|
3572
|
+
const parentOrder = ancestorsOf(p, blockId).map((b) => b.id);
|
|
3573
|
+
const rank = (id) => feeds.has(id) ? 0 : parents.has(id) ? 1 + (parentOrder.indexOf(id) + 1) / (parentOrder.length + 1) : 2;
|
|
3574
|
+
const related = full.blocks.filter((b) => b.id !== blockId).sort((a, b) => rank(a.id) - rank(b.id)).map((b) => {
|
|
3575
|
+
const d = split(b.decisions);
|
|
3576
|
+
const hasScope = !!b.scope && Object.values(b.scope).some((v) => typeof v === "string" && v.trim() !== "");
|
|
3577
|
+
const keepDescription = !hasScope && !!b.description;
|
|
3578
|
+
if (hasScope && b.description) omitted.descriptions++;
|
|
3579
|
+
omitted.ports += b.ports.length;
|
|
3580
|
+
omitted.decisionHistory += d.history;
|
|
3581
|
+
if (b.handoff) omitted.handoffs++;
|
|
3582
|
+
omitted.artifacts += b.artifacts.length + b.ports.reduce((n, port2) => n + (shownPorts.has(port2.id) ? 0 : port2.artifacts.length), 0);
|
|
3583
|
+
return {
|
|
3584
|
+
key: b.key,
|
|
3585
|
+
title: b.title,
|
|
3586
|
+
relation: parents.has(b.id) ? "parent" : "upstream",
|
|
3587
|
+
status: b.freshness.status,
|
|
3588
|
+
...hasScope && !parents.has(b.id) ? { scope: b.scope } : {},
|
|
3589
|
+
...keepDescription ? { description: b.description } : {},
|
|
3590
|
+
...feeds.has(b.id) ? { feedsTarget: feeds.get(b.id) } : {},
|
|
3591
|
+
...d.answered.length ? { decisions: d.answered } : {},
|
|
3592
|
+
...d.open.length ? { openQuestions: d.open } : {}
|
|
3593
|
+
};
|
|
3594
|
+
});
|
|
3595
|
+
omitted.connections = full.connections.filter((e) => p.ports[e.to.portId]?.blockId !== blockId && p.ports[e.from.portId]?.blockId !== blockId).length;
|
|
3596
|
+
return {
|
|
3597
|
+
brief: true,
|
|
3598
|
+
workScope: full.workScope,
|
|
3599
|
+
focus: full.focus,
|
|
3600
|
+
workflowPolicy: full.workflowPolicy,
|
|
3601
|
+
project: full.project,
|
|
3602
|
+
task: full.task,
|
|
3603
|
+
instructions: full.instructions,
|
|
3604
|
+
target: {
|
|
3605
|
+
id: target.id,
|
|
3606
|
+
key: target.key,
|
|
3607
|
+
title: target.title,
|
|
3608
|
+
description: target.description,
|
|
3609
|
+
status: target.freshness.status,
|
|
3610
|
+
freshness: target.freshness,
|
|
3611
|
+
decisions: mine.answered,
|
|
3612
|
+
openQuestions: mine.open,
|
|
3613
|
+
handoff: target.handoff,
|
|
3614
|
+
inputs,
|
|
3615
|
+
outputs,
|
|
3616
|
+
artifacts: target.artifacts,
|
|
3617
|
+
missingInputs: inputs.filter((i) => i.missing).map((i) => i.name)
|
|
3618
|
+
},
|
|
3619
|
+
related,
|
|
3620
|
+
omitted: {
|
|
3621
|
+
...Object.fromEntries(Object.entries(omitted).filter(([, n]) => n > 0)),
|
|
3622
|
+
relatedBlocks: related.length,
|
|
3623
|
+
howToGet: "Counts are what was left out of the related boxes (ancestors and upstream). boxglow context <block> prints everything (same contextToken). For one related box in full: boxglow context <its key>."
|
|
3624
|
+
}
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3264
3627
|
|
|
3265
3628
|
// cli/context.ts
|
|
3266
3629
|
function stable(value) {
|
|
@@ -3281,11 +3644,15 @@ function contextReceipt(p, id) {
|
|
|
3281
3644
|
...b,
|
|
3282
3645
|
artifacts: artifactsForToken(b.artifacts),
|
|
3283
3646
|
ports: b.ports.map((port2) => ({ ...port2, artifacts: artifactsForToken(port2.artifacts) }))
|
|
3284
|
-
}))
|
|
3647
|
+
})),
|
|
3648
|
+
bundledOutputs: context.bundledOutputs.map((o) => ({ ...o, artifacts: artifactsForToken(o.artifacts) }))
|
|
3285
3649
|
};
|
|
3286
3650
|
const token = createHash2("sha256").update(JSON.stringify(stable(forToken))).digest("hex");
|
|
3287
3651
|
return { contextToken: token, context };
|
|
3288
3652
|
}
|
|
3653
|
+
function briefReceipt(p, id) {
|
|
3654
|
+
return { contextToken: contextReceipt(p, id).contextToken, context: briefContext(p, id) };
|
|
3655
|
+
}
|
|
3289
3656
|
function requireContext(p, id, token, actor) {
|
|
3290
3657
|
if (!p.contextGuard || actor !== void 0 && isHumanActor(actor)) return;
|
|
3291
3658
|
if (token !== contextReceipt(p, id).contextToken) {
|
|
@@ -3369,164 +3736,2056 @@ ${END}
|
|
|
3369
3736
|
return changed.length ? changed : [t("\u5909\u66F4\u306A\u3057")];
|
|
3370
3737
|
}
|
|
3371
3738
|
|
|
3372
|
-
//
|
|
3373
|
-
|
|
3374
|
-
const s = summarize(project);
|
|
3375
|
-
const all = Object.entries(project.handoffs ?? {}).filter(([id, note]) => project.blocks[id] && note.note.trim()).map(([blockId, note]) => {
|
|
3376
|
-
const b = project.blocks[blockId];
|
|
3377
|
-
return {
|
|
3378
|
-
blockId,
|
|
3379
|
-
key: b.key,
|
|
3380
|
-
title: b.title,
|
|
3381
|
-
status: b.status,
|
|
3382
|
-
...note,
|
|
3383
|
-
freshness: noteFreshness(b, note.at),
|
|
3384
|
-
freshnessText: freshnessText(b, note.at),
|
|
3385
|
-
statusChangedAt: b.statusChangedAt
|
|
3386
|
-
};
|
|
3387
|
-
}).sort((a, b) => b.at.localeCompare(a.at) || a.blockId.localeCompare(b.blockId));
|
|
3388
|
-
const nextGroups = candidateGroups(project, s.next).map((g) => ({ ...g, items: g.items.slice(0, 10) }));
|
|
3389
|
-
return {
|
|
3390
|
-
project: project.name,
|
|
3391
|
-
focus: project.focusBlockId ? { blockId: project.focusBlockId, title: project.blocks[project.focusBlockId]?.title } : void 0,
|
|
3392
|
-
active: [...s.working, ...s.blocked].map(({ block: block2, actor, note }) => ({ blockId: block2.id, key: block2.key, title: block2.title, actor, note })),
|
|
3393
|
-
pendingDecisions: s.decisions.map(({ block: block2, decision: decision2 }) => ({ key: block2.key, title: block2.title, question: decision2.question })),
|
|
3394
|
-
unreadAnswers: s.answered.map(({ block: block2, decision: decision2 }) => ({ blockId: block2.id, key: block2.key, title: block2.title, question: decision2.question, answer: decision2.answer, answeredAt: decision2.answeredAt })),
|
|
3395
|
-
nextGroups,
|
|
3396
|
-
// 既存の利用側が next を参照していても読めるよう、平らな一覧も残す。
|
|
3397
|
-
next: nextGroups.flatMap((g) => g.items),
|
|
3398
|
-
handoffs: all.filter((h) => h.status !== "white" || options.includeCompleted),
|
|
3399
|
-
completedHandoffCount: all.filter((h) => h.status === "white").length,
|
|
3400
|
-
descriptionReminders: Object.values(project.blocks).filter((b) => descriptionReminder(b)).map((b) => ({ blockId: b.id, key: b.key, title: b.title, message: descriptionReminder(b), writtenAt: b.descriptionUpdatedAt })),
|
|
3401
|
-
instructions: t("\u4F5C\u696D\u306E\u524D\u306B boxglow context <block> \u3092\u8AAD\u307F\u3001guard \u4ED8\u304D\u306E\u5909\u66F4\u306B\u306F\u305D\u306E contextToken \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002\u3053\u306E\u6982\u8981\u306F\u56DE\u7B54\u3092\u78BA\u8A8D\u6E08\u307F\u306B\u305B\u305A\u3001\u64CD\u4F5C\u3092\u8A31\u53EF\u3059\u308B\u3082\u306E\u3067\u3082\u3042\u308A\u307E\u305B\u3093\u3002")
|
|
3402
|
-
};
|
|
3403
|
-
}
|
|
3404
|
-
function resumeReport(project, options = {}) {
|
|
3405
|
-
const s = resumeSummary(project, options);
|
|
3406
|
-
return [
|
|
3407
|
-
"# " + s.project + " \u2014 " + t("\u518D\u958B"),
|
|
3408
|
-
// 今回の範囲のボックスが完了していたら、候補の優先がもう意味を持たないので、選び直しを促す
|
|
3409
|
-
...s.focus ? [t("\u4ECA\u56DE\u306E\u7BC4\u56F2: {title}", { title: s.focus.title ?? "" }) + (project.blocks[s.focus.blockId]?.status === "white" ? " \u2014 " + t("\u5B8C\u4E86\u6E08\u307F\u3067\u3059\u3002focus \u3067\u6B21\u306E\u5BFE\u8C61\u3092\u9078\u3076\u304B\u3001focus none \u3067\u89E3\u9664\u3057\u3066\u304F\u3060\u3055\u3044") : "")] : [],
|
|
3410
|
-
"## " + t("\u4F5C\u696D\u4E2D\u30FB\u78BA\u8A8D\u5F85\u3061"),
|
|
3411
|
-
...s.active.map((a) => "- " + a.key + " " + a.title + " (" + a.actor + "): " + (a.note ?? "")),
|
|
3412
|
-
"## " + t("\u5224\u65AD\u5F85\u3061"),
|
|
3413
|
-
...s.pendingDecisions.map((a) => "- " + a.key + " " + a.title + ": " + a.question),
|
|
3414
|
-
"## " + t("AI\u672A\u78BA\u8A8D\u306E\u56DE\u7B54"),
|
|
3415
|
-
...s.unreadAnswers.map((a) => "- " + a.key + " " + a.title + "\n " + a.question + "\n \u2192 " + a.answer),
|
|
3416
|
-
"## " + t("\u6B21\u306E\u5019\u88DC"),
|
|
3417
|
-
...s.nextGroups.flatMap((g) => ["### " + g.title, ...g.items.map((a) => "- " + a.key + " " + a.title + (a.missingInputs.length ? " \u2014 " + t("\u5FC5\u9808\u306E\u5165\u529B\u5F85\u3061: {names}", { names: a.missingInputs.join(", ") }) : ""))]),
|
|
3418
|
-
"## " + t("\u5F15\u304D\u7D99\u304E\uFF08\u65B0\u3057\u3044\u9806\uFF09"),
|
|
3419
|
-
...s.handoffs.map((h) => "- " + h.key + " " + h.title + " (" + h.actor + ", " + (h.status === "white" ? "Done" : h.status === "gray" ? "In Progress" : "New") + ")\n " + h.freshnessText + "\n" + h.note),
|
|
3420
|
-
...!options.includeCompleted && s.completedHandoffCount ? [t("\u5B8C\u4E86\u6E08\u307F\u306E\u5F15\u304D\u7D99\u304E {n} \u4EF6\u3002resume --include-completed \u3067\u8868\u793A\u3067\u304D\u307E\u3059\u3002", { n: s.completedHandoffCount })] : [],
|
|
3421
|
-
...s.descriptionReminders.length ? [t("\u8AAC\u660E\u306E\u898B\u76F4\u3057\u5019\u88DC: {names} (show \u3067\u78BA\u8A8D)", { names: s.descriptionReminders.map((x) => x.key ?? x.title).join(", ") })] : [],
|
|
3422
|
-
"",
|
|
3423
|
-
s.instructions
|
|
3424
|
-
].join("\n");
|
|
3425
|
-
}
|
|
3426
|
-
|
|
3427
|
-
// cli/main.ts
|
|
3428
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3429
|
-
|
|
3430
|
-
// docs/AGENTS_SNIPPET.md
|
|
3431
|
-
var AGENTS_SNIPPET_default = '# Boxglow \u3092\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u4F7F\u308F\u305B\u308B\u6307\u793A\u66F8 (AGENTS.md / CLAUDE.md \u306B\u8CBC\u308B)\n\n\u4E0B\u306E\u300C---\u300D\u306E\u9593\u3092\u305D\u306E\u307E\u307E\u30EA\u30DD\u30B8\u30C8\u30EA\u306E AGENTS.md (Codex) \u3084 CLAUDE.md (Claude Code) \u306B\u8CBC\u308B\u3002\nboxglow.json \u306F\u30EA\u30DD\u30B8\u30C8\u30EA\u76F4\u4E0B\u306B\u7F6E\u304F (`npx boxglow init --name "<\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D>"`)\u3002\n\n---\n\n## Boxglow (\u8A08\u753B\u3068\u9032\u6357\u306E\u5171\u6709)\n\n\u3053\u306E\u30EA\u30DD\u30B8\u30C8\u30EA\u306E\u8A08\u753B\u3068\u9032\u6357\u306F `boxglow.json` \u304C\u6B63\u672C\u3002\u4EBA\u9593\u306F Boxglow \u306E\u753B\u9762\u3067\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u898B\u3066\u3044\u308B\u3002\n\u30D5\u30A1\u30A4\u30EB\u3092\u76F4\u63A5\u7DE8\u96C6\u305B\u305A\u3001\u5FC5\u305A `npx boxglow` \u30B3\u30DE\u30F3\u30C9\u3067\u66F4\u65B0\u3059\u308B\u3053\u3068 (\u7D50\u7DDA\u30EB\u30FC\u30EB\u3068\u5C65\u6B74\u304C\u4FDD\u305F\u308C\u308B)\u3002\n\n\u30D6\u30ED\u30C3\u30AF = \u300C\u5165\u529B\u304B\u3089\u51FA\u529B (\u6210\u679C\u7269) \u3092\u4F5C\u308B\u30BF\u30B9\u30AF\u300D\u3002\u51FA\u529B\u306E\u6210\u679C\u7269\u304C\u78BA\u5B9A\u3057\u305F\u3089\u5B8C\u4E86 (WhiteBox)\u3002\n\u72B6\u614B: BlackBox (\u51FA\u529B\u3060\u3051\u6C7A\u3081\u305F) \u2192 GrayBox (\u5206\u89E3\u4E2D\u30FB\u7740\u624B\u4E2D) \u2192 WhiteBox (\u5B8C\u4E86)\u3002\n\n### \u4F5C\u696D\u306E\u305F\u3073\u306B\u5B88\u308B\u3053\u3068\n\n1. **\u6700\u521D\u306B\u8AAD\u3080**: `npx boxglow resume` (\u307E\u305F\u306F `npx boxglow status --brief`) \u3067\u3001\u4F5C\u696D\u4E2D\u30FB\u5224\u65AD\u5F85\u3061\u30FBAI \u672A\u78BA\u8A8D\u306E\u56DE\u7B54\u30FB\u6B21\u306E\u5019\u88DC\u30FB\u672A\u5B8C\u4E86\u306E\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u8AAD\u3080\u3002\u524D\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u7D9A\u304D\u306F\u3001\u3053\u3053\u306B\u51FA\u308B\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u304B\u3089\u518D\u958B\u3059\u308B\u3002`npx boxglow show <block>` \u3067\u62C5\u5F53\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u306E\u5165\u51FA\u529B\u3092\u78BA\u8A8D\u3059\u308B\n2. **\u7740\u624B\u3092\u8A18\u9332**: \u30DC\u30C3\u30AF\u30B9\u306B\u53D6\u308A\u304B\u304B\u308B\u3068\u304D `npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>"`\u3002\u540C\u6642\u306B\u9032\u3081\u308B\u30DC\u30C3\u30AF\u30B9\u306F 1\u301C2 \u500B\u307E\u3067\u3002\n \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u304C\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001\u5148\u306B `npx boxglow context <block>` \u3092\u8AAD\u307F\u3001\u51FA\u529B\u306E `contextToken` (\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3) \u3092\u4ED8\u3051\u308B:\n `npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` (\u4E0B\u306E\u300C\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3068\u5F15\u304D\u7D99\u304E\u300D\u3092\u53C2\u7167)\n3. **\u5206\u89E3**: 1 \u3064\u306E\u30DC\u30C3\u30AF\u30B9\u3067\u51FA\u529B\u3092\u4F5C\u308B\u898B\u901A\u3057\u304C\u7ACB\u305F\u306A\u3044\u3068\u304D\u306F\u3001`npx boxglow split <block> --spec \'<JSON>\'` \u3067\u4E2D\u306B\u5C0F\u3055\u306A\u30DC\u30C3\u30AF\u30B9\u3092\u7F6E\u304F\u3002\u5404\u30DC\u30C3\u30AF\u30B9\u306B\u51FA\u529B (\u6210\u679C\u7269) \u3092\u5FC5\u305A\u6C7A\u3081\u308B\u3002\u5165\u529B\u306F\u4E0D\u660E\u306A\u3089\u7701\u7565\u3057\u3066\u3088\u3044 (\u81EA\u52D5\u3067\u4E0A\u306E\u968E\u5C64\u306E\u5165\u529B\u306B\u306A\u308B)\n4. **\u5B8C\u4E86\u3092\u8A18\u9332**: \u51FA\u529B\u306E\u6210\u679C\u7269\u304C\u3067\u304D\u305F\u3089 `npx boxglow done <block> --artifact "<\u540D\u524D>=<\u30EA\u30DD\u30B8\u30C8\u30EA\u5185\u306E\u30D1\u30B9>"`\u3002\n guard \u304C\u6709\u52B9\u306A\u3089 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3092\u4ED8\u3051\u308B (\u76F4\u524D\u306E\u81EA\u5206\u306E\u64CD\u4F5C\u306E\u51FA\u529B\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u3066\u3044\u308C\u3070\u3001\u305D\u308C\u3092\u4F7F\u3046)\u3002\n HEAD \u306B\u30B3\u30DF\u30C3\u30C8\u6E08\u307F\u3067\u5185\u5BB9\u304C\u4E00\u81F4\u3059\u308B\u30D5\u30A1\u30A4\u30EB\u306F\u300C\u30B3\u30DF\u30C3\u30C8 + \u30D1\u30B9 + \u4E2D\u8EAB\u306E\u30CF\u30C3\u30B7\u30E5\u300D\u3067\u8A18\u9332\u3055\u308C\u308B (\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306F\u3057\u306A\u3044)\u3002\u672A\u30B3\u30DF\u30C3\u30C8\u30FB\u672A\u8FFD\u8DE1\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u30ED\u30FC\u30AB\u30EB\u53C2\u7167\u3068\u3057\u3066\u6B8B\u308A\u3001\u30B3\u30DF\u30C3\u30C8\u3057\u305F\u5F8C\u306E `npx boxglow check` \u3067 Git \u306E\u53C2\u7167\u306B\u88DC\u5B8C\u3055\u308C\u308B\u3002\u8A18\u9332\u306E\u305F\u3081\u3060\u3051\u306B\u30B3\u30DF\u30C3\u30C8\u3057\u306A\u3044\u3002\u5168\u968E\u5C64\u304C\u5FC5\u8981\u306A\u3089 `npx boxglow status` \u3092\u8AAD\u3080\u3002PR \u3084\u5916\u90E8\u8CC7\u6599\u306F URL \u3067\u3082\u3088\u3044\u3002\n \u6210\u679C\u7269\u3060\u3051\u5148\u306B\u4ED8\u3051\u308B\u306A\u3089 `npx boxglow artifact <block> <\u30D1\u30B9>`\u3002\u9032\u6357\u306E\u9014\u4E2D\u7D4C\u904E\u306F `npx boxglow set <block> --progress 60`\n5. **\u4EBA\u9593\u306E\u5224\u65AD\u304C\u8981\u308B**: `npx boxglow ask <block> "<\u8CEA\u554F>" --options "A|B"`\u3002\u56DE\u7B54\u304C\u3042\u308B\u307E\u3067\u305D\u306E\u30DC\u30C3\u30AF\u30B9\u306F\u9032\u3081\u305A\u3001\u4ED6\u306E\u30DC\u30C3\u30AF\u30B9\u3078\u79FB\u308B\u3002\u4EBA\u306E\u56DE\u7B54\u306F `status` \u306E\u300C\u56DE\u7B54\u3042\u308A\u300D\u306B\u51FA\u308B\u3002\u8AAD\u3093\u3060\u3089 `npx boxglow ack <block>` \u3067\u5F15\u304D\u53D6\u308B (\u305D\u306E\u30DC\u30C3\u30AF\u30B9\u306E `start` / `done` / `set` \u306A\u3069\u3067\u3082\u81EA\u52D5\u3067\u5F15\u304D\u53D6\u3089\u308C\u308B)\u3002\u5F15\u304D\u53D6\u308B\u307E\u3067\u4EBA\u306E\u753B\u9762\u306B\u306F\u300CAI \u672A\u78BA\u8A8D\u300D\u3068\u3057\u3066\u6B8B\u308A\u3001\u4EBA\u304C\u7B54\u3048\u3092\u76F4\u305B\u308B\u3002\u56DE\u7B54\u306E\u4E2D\u306B\u554F\u3044\u8FD4\u3057\u304C\u3042\u308C\u3070\u3001`ask` \u3068 `answer --by <\u81EA\u5206>` \u3067\u81EA\u5206\u306E\u7B54\u3048\u3082\u8A18\u9332\u3059\u308B\n6. **\u8A70\u307E\u3063\u305F\u3089**: `npx boxglow blocked <block> --note "<\u56F0\u3063\u3066\u3044\u308B\u3053\u3068>"`\n7. **\u6210\u679C\u7269\u306E\u78BA\u8A8D**: \u30D5\u30A1\u30A4\u30EB\u3092\u79FB\u52D5\u30FB\u6539\u540D\u3057\u305F\u3089 `npx boxglow check` \u3092\u5B9F\u884C\u3059\u308B (\u79FB\u52D5\u3092\u691C\u51FA\u3057\u3066\u30D1\u30B9\u3092\u4ED8\u3051\u66FF\u3048\u308B\u3002\u898B\u3064\u304B\u3089\u306A\u3051\u308C\u3070\u5370\u304C\u4ED8\u304F)\n8. **\u4E2D\u65AD\u30FB\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B**: \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u7D42\u308F\u308A\u30FB\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u5727\u7E2E\u30FB\u4ED6\u306E AI \u3084\u4EBA\u3078\u306E\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B\u3001`npx boxglow checkpoint <block> --note "\u5206\u304B\u3063\u305F\u3053\u3068; \u6B21\u306B\u3059\u308B\u3053\u3068; \u672A\u89E3\u6C7A\u306E\u3053\u3068"` \u3067\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u6B8B\u3059 (guard \u304C\u6709\u52B9\u306A\u3089 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3092\u4ED8\u3051\u308B)\u3002\u4F1A\u8A71\u306E\u5C65\u6B74\u304C\u6D88\u3048\u3066\u3082\u8A08\u753B\u306E\u4E2D\u306B\u6B8B\u308A\u3001\u6B21\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u304C `resume` \u3068 `context` \u3067\u8AAD\u3080\n9. **\u5831\u544A**: \u4F5C\u696D\u306E\u6700\u5F8C\u306B `npx boxglow status --brief` \u306E\u5185\u5BB9\u3092\u8981\u7D04\u3057\u3066\u5831\u544A\u3059\u308B\n\n### \u69CB\u9020\u306E\u7D04\u675F\n\n- \u6700\u4E0A\u4F4D\u306B\u306F\u300C\u5165\u529B\u30CE\u30FC\u30C9\u300D\u300C\u6700\u7D42\u6210\u679C\u7269\u30CE\u30FC\u30C9\u300D\u3068\u300C\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u300D\u3060\u3051\u304C\u3042\u308B\u3002\u30BF\u30B9\u30AF\u306F\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u4E2D\u306B\u7F6E\u304F (`add` \u306E\u65E2\u5B9A\u306E\u89AA\u306F\u6700\u521D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9)\n- \u968E\u5C64\u306E\u6DF1\u3055\u306B\u5236\u9650\u306F\u306A\u3044\u3002`split` \u306F\u4F55\u6BB5\u3067\u3082\u4F7F\u3048\u308B\n- \u5225\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u3082\u4F7F\u3048\u305D\u3046\u306A\u30DC\u30C3\u30AF\u30B9 (\u4F8B: \u5165\u529B\u753B\u50CF\u306E\u30E2\u30CE\u30AF\u30ED\u5316) \u306F `npx boxglow export-block <block> --out <\u540D\u524D>.boxglow-block.json` \u3067\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306B\u3057\u3001\n `npx boxglow import-block <path> --parent <block>` \u3067\u633F\u5165\u3059\u308B\u3002\u30EA\u30DD\u30B8\u30C8\u30EA\u306E `boxglow-blocks/` \u306B\u7F6E\u3044\u3066\u5171\u6709\u3057\u3066\u3088\u3044\n\n\u6700\u4E0A\u4F4D\u306E\u5165\u529B\u304C\u591A\u3044\u3068\u304D\u306F `npx boxglow group "<\u30B0\u30EB\u30FC\u30D7\u540D>"` \u3067\u30B0\u30EB\u30FC\u30D7\u3092\u4F5C\u308A\u3001`npx boxglow group-set <\u5165\u529B\u540D> <\u30B0\u30EB\u30FC\u30D7\u540D>` \u3067\u5206\u3051\u308B (\u4F8B: PCIe \u4ED5\u69D8\u66F8 / DDR \u4ED5\u69D8\u66F8)\u3002\n\n### split \u306E JSON \u5F62\u5F0F\n\n```json\n{\n "blocks": [\n { "title": "\u8A2D\u8A08", "description": "\u3084\u308B\u3053\u3068", "inputs": ["\u4ED5\u69D8"], "outputs": ["\u8A2D\u8A08\u66F8"] },\n { "title": "\u5B9F\u88C5", "inputs": ["\u8A2D\u8A08\u66F8"], "outputs": ["\u30B3\u30FC\u30C9"] }\n ],\n "connections": [\n { "from": "parent.\u4ED5\u69D8", "to": "\u8A2D\u8A08.\u4ED5\u69D8" },\n { "from": "\u8A2D\u8A08.\u8A2D\u8A08\u66F8", "to": "\u5B9F\u88C5.\u8A2D\u8A08\u66F8" },\n { "from": "\u5B9F\u88C5.\u30B3\u30FC\u30C9", "to": "parent.\u51FA\u529B" }\n ]\n}\n```\n\n`parent.<\u540D\u524D>` \u306F\u5206\u89E3\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u81EA\u8EAB\u306E\u5165\u529B / \u51FA\u529B\u3002`<block>` \u306F\u77ED\u3044 ID (B12 \u306A\u3069\u3002status \u306B\u51FA\u308B) \u304B\u984C\u540D\u3002\n\u4E0B\u306E\u968E\u5C64\u3092\u6301\u305F\u306A\u3044\u30DC\u30C3\u30AF\u30B9\u306E\u51FA\u529B\u306F 1 \u672C (outputs \u306F 1 \u3064\u3060\u3051\u66F8\u304F)\u3002\u984C\u540D\u306F\u300C\u4F55\u3092\u4F5C\u308B\u304B\u300D\u304C\u5206\u304B\u308B\u77ED\u3044\u8A00\u8449\u306B\u3059\u308B\u3002\n\u671F\u65E5\u3084\u6642\u9593\u306F `npx boxglow set <block> --due 2026-10-15 --start 2026-10-01 --estimate 8 --hours 3.5` \u3067\u8A18\u9332\u3059\u308B\u3002\n\u30DC\u30C3\u30AF\u30B9\u306B\u306F\u4ED5\u4E8B\u306E\u7A2E\u985E (\u30AB\u30C6\u30B4\u30EA) \u3092\u4ED8\u3051\u308B: `--category design` (\u8A2D\u8A08) / build (\u5B9F\u88C5) / verify (\u691C\u8A3C) / evaluate (\u8A55\u4FA1) / study (\u691C\u8A0E) / research (\u8ABF\u67FB) / ui (\u30C7\u30B6\u30A4\u30F3) / improve (\u6539\u5584) / fix (\u8AB2\u984C\u89E3\u6C7A) / docs (\u6587\u66F8) / ops (\u904B\u7528) / other (\u305D\u306E\u4ED6: \u8FF7\u3063\u305F\u3068\u304D)\u3002add \u306E\u3068\u304D\u306B\u4ED8\u3051\u3001\u5909\u3048\u308B\u306A\u3089 `npx boxglow set <block> --category verify`\u3002\n\u7D50\u7DDA\u306E\u53D7\u3051\u5074\u306F\u984C\u540D\u3060\u3051\u3067\u3088\u3044 (`npx boxglow connect "A.\u8A2D\u8A08\u66F8" "B"` \u3067 B \u306B\u5165\u529B\u300C\u8A2D\u8A08\u66F8\u300D\u304C\u4F5C\u3089\u308C\u3066\u3064\u306A\u304C\u308B\u3002split \u306E connections \u3082 `to: "B"` \u3067\u3088\u3044)\u3002\u3064\u306A\u3044\u3060\u5165\u529B\u306E\u540D\u524D\u306F\u4F9B\u7D66\u5143\u306E\u51FA\u529B\u540D\u306B\u306A\u308A\u3001\u5165\u529B\u5074\u3067\u306F\u5909\u3048\u3089\u308C\u306A\u3044 (\u5909\u3048\u308B\u306A\u3089\u4F9B\u7D66\u5143\u306E\u51FA\u529B\u306E\u540D\u524D\u3092\u5909\u3048\u308B\u3002\u3064\u306A\u304C\u308B\u5148\u3082\u4E00\u7DD2\u306B\u5909\u308F\u308B)\u3002\u65E2\u5B58\u306E\u30DC\u30C3\u30AF\u30B9\u306B\u5165\u51FA\u529B\u3092\u8DB3\u3059 / \u540D\u524D\u3092\u5909\u3048\u308B\u306E\u306F `npx boxglow port <block> --in <\u540D\u524D> --out <\u540D\u524D> --rename <\u65E7>=<\u65B0>` (`project` = \u6700\u521D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u3002\u6700\u7D42\u6210\u679C\u7269\u306E\u540D\u524D\u3082\u3053\u308C\u3067\u5909\u3048\u308B)\u3002\u7DDA\u3092\u5916\u3059\u306E\u306F `npx boxglow disconnect <\u984C\u540D.\u51FA\u529B\u540D> <\u984C\u540D.\u5165\u529B\u540D>`\u3001\u30DC\u30C3\u30AF\u30B9\u305D\u306E\u3082\u306E\u3092\u6D88\u3059\u306E\u306F `npx boxglow remove <block>` (\u4E2D\u306B\u30DC\u30C3\u30AF\u30B9\u304C\u3042\u308C\u3070 `--force`)\u3002MCP \u30C4\u30FC\u30EB (`boxglow_status` / `boxglow_start` / `boxglow_done` / `boxglow_ask` \u306A\u3069\u3002\u5229\u7528\u3059\u308B\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u767B\u9332\u6E08\u307F\u306A\u3089\u4F7F\u3048\u308B\u3002`.mcp.json` \u306F Claude Code \u5411\u3051\u3067\u3001Codex \u306F\u5225\u9014\u767B\u9332\u3059\u308B) \u306F\u540C\u3058\u30B3\u30DE\u30F3\u30C9\u306E\u5225\u306E\u5165\u53E3\u3067\u3001\u3069\u3061\u3089\u3067\u66F8\u3044\u3066\u3082\u540C\u3058\u30D5\u30A1\u30A4\u30EB\u304C\u66F4\u65B0\u3055\u308C\u308B\u3002\u8A08\u753B\u306E\u6587\u66F8\u306F `npx boxglow export --out docs/ROADMAP.md`\u3002\n\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u306F\u3001\u65B9\u91DD (README \u3084\u4F9D\u983C\u6587) \u304B\u3089\u6700\u4E0A\u4F4D\u306E\u5927\u9805\u76EE 3\u301C7 \u500B\u3092\u5148\u306B\u4F5C\u308A\u3001\u4EBA\u306B `ask` \u3067\u78BA\u8A8D\u3057\u3066\u304B\u3089\u3001\u7740\u624B\u3059\u308B\u5927\u9805\u76EE\u3060\u3051\u3092 `split` \u3067\u5206\u89E3\u3059\u308B (\u5148\u306E\u6BB5\u968E\u306F\u7C97\u3044\u307E\u307E)\u3002`ask` \u306E\u8CEA\u554F\u306F\u305D\u308C\u3060\u3051\u3067\u5224\u65AD\u3067\u304D\u308B\u3088\u3046\u306B\u66F8\u304D\u3001\u524D\u63D0\u30FB\u6BD4\u8F03\u30FB\u5F71\u97FF\u306F `--context` \u306B\u5165\u308C\u308B (\u300C\u3053\u308C\u3067\u3088\u3044\u3067\u3059\u304B\u300D\u306E\u3088\u3046\u306B\u5916\u3092\u6307\u3055\u306A\u3044)\u3002\u5019\u88DC\u304B\u3089 1 \u3064\u3092\u9078\u3076\u5834\u9762\u3067\u306F\u3001\u81EA\u5206\u3067\u6C7A\u3081\u308B\u3068\u304D\u3082 `ask ... --options "A|B|C"` \u3068 `answer --by <\u81EA\u5206>` \u3067\u8A18\u9332\u3057\u3001\u9078\u3070\u306A\u304B\u3063\u305F\u5019\u88DC\u3092\u6B8B\u3059\u3002\u65B9\u91DD\u8EE2\u63DB\u306F `reopen <block> --note <\u7406\u7531>`\u3002\u3059\u3079\u3066 CLI \u3060\u3051\u3067\u6210\u7ACB\u3059\u308B (\u753B\u9762\u306F\u4EBA\u304C\u898B\u308B\u305F\u3081\u306E\u3082\u306E)\u3002\u4EBA\u304C\u3044\u306A\u3044\u904B\u7528\u306A\u3089\u3001AI \u304C `answer --by <\u81EA\u5206>` \u3067\u5224\u65AD\u3092\u8A18\u9332\u3057\u3066\u9032\u3081\u308B\u3002\n\u51FA\u529B\u306E\u540D\u524D\u306F\u300C\u5177\u4F53\u7684\u306A\u6210\u679C\u7269\u300D\u306B\u3059\u308B (\u4F8B: `\u8A2D\u8A08\u66F8 docs/design.md`\u3001`PR #12`\u3001`\u516C\u958B URL`\u3001`\u30C6\u30B9\u30C8\u7D50\u679C (vitest 53 \u4EF6)`)\u3002\u300C\u6A5F\u80FD\u4E00\u5F0F\u300D\u300C\u6240\u898B\u300D\u306E\u3088\u3046\u306A\u62BD\u8C61\u7684\u306A\u540D\u524D\u306F\u907F\u3051\u3001done \u306E\u3068\u304D\u306F\u5FC5\u305A --artifact \u3067\u30D5\u30A1\u30A4\u30EB\u30FBURL\u30FB\u30B3\u30DF\u30C3\u30C8\u3092\u4ED8\u3051\u308B\u3002\n\u3059\u3079\u3066\u306E\u30DC\u30C3\u30AF\u30B9\u306F\u300C\u5165\u529B \u2192 \u51FA\u529B\u300D\u304C\u4F55\u304B\u306B\u3064\u306A\u304C\u3063\u3066\u3044\u308B\u3088\u3046\u306B\u3059\u308B (\u3064\u306A\u304C\u3063\u3066\u3044\u306A\u3044\u30DC\u30C3\u30AF\u30B9\u306F\u8A08\u753B\u306E\u7A74\u3002\u6700\u4E0A\u4F4D\u306E\u5165\u529B\u30CE\u30FC\u30C9\u306F\u3001\u672A\u63A5\u7D9A\u306E\u5165\u529B\u304C\u81EA\u52D5\u3067\u4E0A\u304C\u308B)\u3002\n\u8907\u6570\u4EBA\u3067\u540C\u3058 boxglow.json \u3092\u7DE8\u96C6\u3059\u308B\u306A\u3089\u3001\u5404\u81EA\u306E clone \u3067\u4E00\u5EA6 `npx boxglow git-setup` \u3092\u5B9F\u884C\u3059\u308B (\u30DC\u30C3\u30AF\u30B9\u306E\u5358\u4F4D\u3067\u81EA\u52D5\u30DE\u30FC\u30B8\u3055\u308C\u3001\u5225\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u5909\u66F4\u306F\u885D\u7A81\u3057\u306A\u3044)\u3002\n\u8907\u6570\u30EA\u30DD\u30B8\u30C8\u30EA\u3092 1 \u3064\u306E\u8A08\u753B\u3067\u7BA1\u7406\u3059\u308B\u3068\u304D\u306F\u3001boxglow.json \u3092\u4E0A\u306E\u30D5\u30A9\u30EB\u30C0\u306B\u7F6E\u304D\u3001\u5404\u30EA\u30DD\u30B8\u30C8\u30EA\u3067\u306F\u74B0\u5883\u5909\u6570 BOXGLOW_FILE \u306B\u305D\u306E\u5834\u6240\u3092\u5165\u308C\u308B (\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u306F\u30EA\u30DD\u30B8\u30C8\u30EA\u3054\u3068\u306B 1 \u3064\u3002`npx boxglow project "\u540D\u524D" --repo <\u30D1\u30B9>`)\u3002\n\u5165\u529B\u306F\u65E2\u5B9A\u3067\u5FC5\u9808\u3002\u7121\u304F\u3066\u3082\u7740\u624B\u3067\u304D\u308B\u5165\u529B\u306F\u753B\u9762\u3067\u300C\u4EFB\u610F\u300D\u306B\u3059\u308B\u3002status \u306E\u300C\u6B21\u306E\u5019\u88DC\u300D\u306F\u5FC5\u9808\u306E\u5165\u529B\u304C\u305D\u308D\u3063\u305F\u30DC\u30C3\u30AF\u30B9\u304B\u3089\u4E26\u3076 (\u300C\u7740\u624B\u3067\u304D\u308B\u300D/\u300C\u5FC5\u9808\u306E\u5165\u529B\u5F85\u3061\u300D\u304C\u4ED8\u304F)\u3002\n\u5165\u529B\u306E\u8AAC\u660E\u306F\u66F8\u304B\u306A\u3044 (\u3064\u306A\u304C\u3063\u305F\u51FA\u529B\u5074\u306E\u8AAC\u660E\u304C\u4F7F\u308F\u308C\u308B)\u3002\u51FA\u529B\u306E\u8AAC\u660E\u306B\u5F62\u5F0F\u30FB\u5236\u7D04\u3092\u66F8\u304F\u3002\n\n\n### \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3068\u5F15\u304D\u7D99\u304E\n\n\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u306F\u3001AI \u304C\u53E4\u3044\u6307\u793A\u306E\u307E\u307E\u4F5C\u696D\u3092\u9032\u3081\u308B\u306E\u3092\u9632\u3050\u4ED5\u7D44\u307F\u3002`setup-agent` \u304C\u6709\u52B9\u306B\u3059\u308B (\u305D\u308C\u4EE5\u5916\u306E\u8A08\u753B\u3067\u306F `npx boxglow guard on`)\u3002\n\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001AI \u306E `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` \u306B `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u304C\u8981\u308B\u3002\u4EBA\u306E\u64CD\u4F5C (`--actor human`) \u306B\u306F\u8981\u6C42\u3055\u308C\u306A\u3044\u3002\n\n```\nnpx boxglow context B12 # \u5224\u65AD (\u5F15\u304D\u53D6\u308A\u6E08\u307F\u306E\u56DE\u7B54\u3092\u542B\u3080)\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u8AAD\u3080\u3002\u51FA\u529B\u306E contextToken \u304C\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\nnpx boxglow start B12 --note "<\u4F55\u3092\u3059\u308B\u304B>" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\nnpx boxglow ask B12 "<\u8CEA\u554F>" --options "A|B" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n# \u2192 \u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C: \u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\nnpx boxglow done B12 --artifact "<\u540D\u524D>=<\u30D1\u30B9>" --context-token <\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n```\n\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u308B\u5834\u5408 (\u8AAD\u307F\u76F4\u3057\u304C\u8981\u308B)**: \u4EBA\u304C\u56DE\u7B54\u3057\u305F\u30FB\u56DE\u7B54\u3092\u76F4\u3057\u305F\u3001\u4EBA\u304C\u6307\u793A (\u984C\u540D\u30FB\u8AAC\u660E\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6) \u3092\u5909\u3048\u305F\u3001\u4ED6\u306E AI \u3084\u4EBA\u304C\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u66F4\u65B0\u3057\u305F\u3002\u89AA\u3084\u5165\u529B\u5143\u306E\u30DC\u30C3\u30AF\u30B9\u3067\u8D77\u304D\u305F\u5834\u5408\u3082\u540C\u3058\u3002\u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u62D2\u5426\u3055\u308C\u308B\u306E\u3067\u3001`context` \u3092\u8AAD\u307F\u76F4\u3057\u3001\u5909\u308F\u3063\u305F\u5185\u5BB9\u3092\u4F5C\u696D\u306B\u53CD\u6620\u3057\u3066\u304B\u3089\u3084\u308A\u76F4\u3059\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u3089\u306A\u3044\u5834\u5408**: `start` / `blocked` / `review` / `leave` / `ack`\u3001\u9032\u6357\u30FB\u72B6\u614B\u30FB\u671F\u65E5\u30FB\u30AB\u30C6\u30B4\u30EA\u306E\u5909\u66F4\u3001`check` \u306B\u3088\u308B\u6210\u679C\u7269\u306E\u78BA\u8A8D\u3001\u753B\u9762\u3067\u306E\u914D\u7F6E\u306E\u5909\u66F4\u3002\u540C\u3058\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u3044\u7D9A\u3051\u3089\u308C\u308B\n- **\u81EA\u5206\u3067\u66F4\u65B0\u3067\u304D\u308B\u5834\u5408**: \u81EA\u5206\u306E `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` \u306A\u3069\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5909\u308F\u3063\u305F\u3068\u304D\u306F\u3001\u305D\u306E\u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u308B\u3002\u81EA\u5206\u3067\u5909\u3048\u305F\u5185\u5BB9\u306F\u5206\u304B\u3063\u3066\u3044\u308B\u306E\u3067\u3001\u8AAD\u307F\u76F4\u3055\u305A\u306B\u6B21\u306E\u64CD\u4F5C\u3078\u4F7F\u3046\u3002`ask` / `decision` / `answer` / `reopen` \u306F\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u7121\u3057\u3067\u3082\u5B9F\u884C\u3067\u304D\u308B\u304C\u3001\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4ED8\u3051\u305F\u3068\u304D\u3060\u3051\n- \u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u901A\u3059\u305F\u3081\u306B guard \u3092\u7121\u52B9\u306B\u3057\u306A\u3044 (AI \u304C `guard off` \u3059\u308B\u306B\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u8981\u308B)\n- MCP \u3067\u306F `boxglow_context` \u3068\u3001\u5404\u30C4\u30FC\u30EB\u306E `contextToken` \u5F15\u6570\u3092\u4F7F\u3046 (\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u30C4\u30FC\u30EB\u306E\u7D50\u679C\u306E\u6700\u5F8C\u306E\u884C\u306B\u51FA\u308B)\n- \u300C\u4ED6\u306E Boxglow \u304C\u66F8\u304D\u8FBC\u307F\u4E2D (\u30ED\u30C3\u30AF\u4E2D)\u300D\u3068\u51FA\u3066\u3001\u5C11\u3057\u5F85\u3063\u3066\u3084\u308A\u76F4\u3057\u3066\u3082\u89E3\u3051\u306A\u3044\u3068\u304D\u306F\u3001`npx boxglow unlock` \u306E\u8868\u793A (\u6301\u3061\u4E3B\u3068\u5224\u5B9A) \u3092\u4EBA\u306B\u898B\u305B\u3066\u89E3\u9664\u3092\u983C\u3080\u3002\u81EA\u5206\u3067 `--actor human` \u3092\u4ED8\u3051\u305F\u308A\u3001\u30ED\u30C3\u30AF\u306E\u30D5\u30A9\u30EB\u30C0\u3092\u76F4\u63A5\u6D88\u3057\u305F\u308A\u3057\u306A\u3044\n\n### \u7740\u624B\u30FB\u5B8C\u4E86\u3001\u4ECA\u56DE\u306E\u7BC4\u56F2\u3001\u8A18\u9332\u306E\u9BAE\u5EA6\n\n- \u6700\u521D\u306B resume \u306E\u73FE\u6CC1 (\u6D3B\u52D5\u30FB\u5224\u65AD\u5F85\u3061\u30FB\u672A\u78BA\u8A8D\u56DE\u7B54) \u3092\u8AAD\u3080\u3002\u5019\u88DC\u306F\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2 / \u305D\u306E\u4ED6\u300D\u306E\u5404\u7D44\u3067\u300C\u7740\u624B\u3067\u304D\u308B / \u5165\u529B\u5F85\u3061\u300D\u306B\u5206\u304B\u308C\u3001\u4E0D\u8DB3\u3059\u308B\u5165\u529B\u540D\u304C\u51FA\u308B\u3002\u5165\u529B\u5F85\u3061\u3092\u3001\u6E96\u5099\u6E08\u307F\u3068\u53D6\u308A\u9055\u3048\u306A\u3044\u3002\n- \u30DC\u30C3\u30AF\u30B9\u306E\u4EFB\u610F4\u9805\u76EE\u3092 scope \u3067\u8A2D\u5B9A\u3059\u308B\u3002goal = \u4ECA\u56DE\u9054\u6210\u3059\u308B\u3053\u3068\u3001non-goals = \u4ECA\u56DE\u306F\u6271\u308F\u306A\u3044\u3053\u3068\u3001acceptance = \u5B8C\u4E86\u3068\u5224\u65AD\u3059\u308B\u6761\u4EF6\u3001consult = \u7BC4\u56F2\u3092\u5E83\u3052\u308B\u524D\u306B\u76F8\u8AC7\u3059\u308B\u6761\u4EF6\u3002context \u306E\u5148\u982D\u3067\u5BFE\u8C61\u3068\u89AA\u306E\u7BC4\u56F2\u3092\u8AAD\u307F\u3001\u7740\u624B\u524D\u3068\u5B8C\u4E86\u524D\u306B\u7167\u5408\u3059\u308B\u3002\u5B8C\u4E86\u6761\u4EF6\u306E\u672C\u6587\u3092AI\u304C\u6E80\u305F\u3057\u305F\u304B\u306F\u81EA\u52D5\u5224\u5B9A\u3057\u306A\u3044\u3002\n- scope B12 --goal "\u9054\u6210\u3059\u308B\u3053\u3068" --non-goals "\u4ECA\u56DE\u6271\u308F\u306A\u3044\u3053\u3068" --acceptance "\u78BA\u8A8D\u3059\u308B\u6761\u4EF6" --consult "\u76F8\u8AC7\u3059\u308B\u6761\u4EF6" \u3067\u8A2D\u5B9A\u3059\u308B\u3002scope B12 \u306F\u8868\u793A\u3060\u3051\u3002\u5404\u9805\u76EE\u306B none \u307E\u305F\u306F\u7A7A\u6587\u5B57\u3092\u6E21\u3059\u3068\u6D88\u3059\u3002\u753B\u9762\u3067\u306F\u8A73\u7D30\u30D1\u30CD\u30EB\u306E \u22EF \u2192\u300C\u4F5C\u696D\u7BC4\u56F2\u3092\u7DE8\u96C6\u300D\u3002\u8A18\u5165\u6E08\u307F\u306E\u9805\u76EE\u3060\u3051\u304C\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2\u300D\u306B\u51FA\u308B\u3002\n- focus B12 \u3067\u3053\u306E\u30DC\u30C3\u30AF\u30B9\u3068\u914D\u4E0B\u3092\u6B21\u5019\u88DC\u306E\u5148\u982D\u306B\u3059\u308B\u3002focus \u306F\u73FE\u5728\u306E\u5BFE\u8C61\u306E\u8868\u793A\u3001focus none \u306F\u89E3\u9664\u3002\u5BFE\u8C61\u5916\u3082\u5225\u306E\u7D44\u306B\u6B8B\u308B\u3002\u5019\u88DC\u306B\u51FA\u308B\u3053\u3068\u81EA\u4F53\u306F\u4F5C\u696D\u7BC4\u56F2\u306E\u62E1\u5927\u3084\u516C\u958B\u3092\u8A31\u53EF\u3057\u306A\u3044\u3002\n- start \u306E\u5FC5\u9808\u5165\u529B\u4E0D\u8DB3\u306F\u65E2\u5B9A\u3067\u8B66\u544A\u3059\u308B\u304C\u3001\u5F93\u6765\u3069\u304A\u308A\u958B\u59CB\u3067\u304D\u308B\u3002\u5148\u884C\u4F5C\u696D\u304C\u5FC5\u8981\u306A\u3089 --reason "\u5148\u306B\u884C\u3046\u7406\u7531" \u3092\u4ED8\u3051\u308B\u3002\u8B66\u544A\u306F\u51FA\u305A\u3001\u7406\u7531\u306F\u6D3B\u52D5\u3068\u30ED\u30B0\u306B\u6B8B\u308B\u3002done \u306E\u51FA\u529B\u6210\u679C\u7269\u306A\u3057\u3082\u65E2\u5B9A\u3067\u8B66\u544A\u306E\u307F\u3002\u65E2\u5B58\u306E\u51FA\u529B\u6210\u679C\u7269\u307E\u305F\u306F --artifact \u3092\u6570\u3048\u3001\u30DC\u30C3\u30AF\u30B9\u306E\u53C2\u8003\u8CC7\u6599\u306F\u6570\u3048\u306A\u3044\u3002\n- \u8A08\u753B\u3067\u9078\u3076\u5834\u5408\u3060\u3051 policy --start reject --done reject \u306B\u3059\u308B\u3002\u305D\u308C\u305E\u308C warn \u306B\u623B\u305B\u308B\u3002\u5165\u529B\u5F85\u3061\u306E\u62D2\u5426\u3067\u3082\u7406\u7531\u4ED8\u304D\u306E\u958B\u59CB\u306F\u53EF\u80FD\u3002\u6210\u679C\u7269\u306A\u3057\u306E\u62D2\u5426\u306F set --status white \u306B\u3082\u52B9\u304F\u3002\u4EBA\u306E\u64CD\u4F5C (\u753B\u9762 / --actor human) \u306F\u62D2\u5426\u3057\u306A\u3044\u3002AI\u304C\u62D2\u5426\u3092\u907F\u3051\u308B\u305F\u3081\u306B\u4EBA\u3092\u540D\u4E57\u3063\u305F\u308A\u3001\u8A2D\u5B9A\u3092\u5F31\u3081\u305F\u308A\u3057\u306A\u3044\u3002\n- guard \u6709\u52B9\u6642\u3001scope \u306E\u5909\u66F4\u3001focus \u306E\u8A2D\u5B9A\u30FB\u89E3\u9664\u3001policy \u306E\u5909\u66F4\u306B\u3082 --context-token \u304C\u5FC5\u8981\u3002scope/focus/policy \u3092\u5909\u3048\u308B\u3068\u65B0\u3057\u3044\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u3067\u3001\u6B21\u306E\u64CD\u4F5C\u306F\u305D\u308C\u3092\u4F7F\u3046\u3002\u8868\u793A\u3060\u3051\u306B\u306F\u4E0D\u8981\u3002\n- resume \u306F\u5B8C\u4E86\u6E08\u307F\u306E\u5F15\u304D\u7D99\u304E\u672C\u6587\u3092\u96A0\u3057\u3001\u4EF6\u6570\u3060\u3051\u793A\u3059\u3002\u5C65\u6B74\u304C\u5FC5\u8981\u306A\u5834\u5408\u3060\u3051 resume --include-completed \u3067\u5C55\u958B\u3059\u308B (context / show \u3067\u306F\u500B\u5225\u306B\u8AAD\u3081\u308B)\u3002\n- \u8AAC\u660E\u306F descriptionUpdatedAt\u3001\u72B6\u614B\u306F statusChangedAt\u3001\u5F15\u304D\u7D99\u304E\u306F\u65E2\u5B58\u306E at \u3067\u65E5\u6642\u3092\u78BA\u8A8D\u3067\u304D\u308B\u3002\u72B6\u614B\u5909\u66F4\u3088\u308A\u53E4\u3044\u30E1\u30E2\u306F\u305D\u306E\u65E8\u3092\u793A\u3059\u3002\u65E7\u8A18\u9332\u306E\u4E0D\u660E\u306A\u65E5\u6642\u306F\u88DC\u308F\u306A\u3044\u3002\u300C\u65B0\u3057\u3044\u8A18\u9332\u300D\u306F\u771F\u507D\u306E\u4FDD\u8A3C\u3067\u306F\u306A\u3044\u3002\n- Done \u3067\u53E4\u3044\u72B6\u6CC1\u8AAC\u660E\u304C\u6B8B\u308B\u53EF\u80FD\u6027\u304C\u3042\u308B\u5834\u5408\u3001CLI\u30FB\u8A73\u7D30\u30D1\u30CD\u30EB\u30FBresume \u306B\u898B\u76F4\u3057\u6848\u5185\u304C\u51FA\u308B\u3002\u672C\u6587\u306F\u81EA\u52D5\u3067\u6D88\u3055\u306A\u3044\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089 set B12 --note "\u73FE\u5728\u306E\u8AAC\u660E" (none \u3067\u6D88\u53BB) \u307E\u305F\u306F\u753B\u9762\u306E Notes \u3067\u76F4\u3059\u3002\n- MCP \u306F boxglow_scope / boxglow_focus / boxglow_policy\u3001boxglow_start \u306E reason\u3001boxglow_resume \u306E includeCompleted \u3092\u4F7F\u3046\u3002\u5909\u66F4\u306B\u306F contextToken \u3092\u4ED8\u3051\u308B\u3002\n- \u30C7\u30FC\u30BF\u5F62\u5F0F\u306F v5 \u306E\u307E\u307E\u3067\u5168\u9805\u76EE\u306F\u4EFB\u610F\u3002\u65E7\u7248 (0.4.2\u4EE5\u524D) \u306F\u65B0\u3057\u3044\u898F\u5247\u3092\u5B9F\u884C\u305B\u305A\u3001\u4FDD\u5B58\u6642\u306B\u8A08\u753B\u306E workflowPolicy / focusBlockId \u3092\u843D\u3068\u3059\u3002\u3053\u308C\u3089\u3092\u4F7F\u3046\u8A08\u753B\u306E\u66F8\u304D\u624B (CLI\u30FBserve\u30FB\u753B\u9762\u30FB\u62E1\u5F35\u5185\u306E\u30A2\u30D7\u30EA) \u306F\u5BFE\u5FDC\u3057\u305F\u30D3\u30EB\u30C9\u306B\u63C3\u3048\u308B\u3002\u7248\u756A\u53F7\u3060\u3051\u3067\u306A\u304F\u30D3\u30EB\u30C9\u65E5\u6642\u3082\u78BA\u8A8D\u3059\u308B\u3002\n\n---\n';
|
|
3432
|
-
|
|
3433
|
-
// docs/AGENTS_SNIPPET.en.md
|
|
3434
|
-
var AGENTS_SNIPPET_en_default = '# Instructions for agents using Boxglow (paste into AGENTS.md / CLAUDE.md)\n\nPaste the part between the "---" lines below, as is, into your repository\'s AGENTS.md (Codex) or CLAUDE.md (Claude Code).\nPut boxglow.json at the repository root (`npx boxglow init --name "<project name>"`).\n\n---\n\n## Boxglow (shared plan and progress)\n\n`boxglow.json` is the source of truth for this repository\'s plan and progress. Humans watch this file on the Boxglow screen.\nDo not edit the file directly; always update it with `npx boxglow` commands (this keeps the wiring rules and the history intact).\n\nA block = "a task that turns inputs into an output (deliverable)". It is done (WhiteBox) once the output\'s deliverable is settled.\nStatus: BlackBox (only the output is decided) \u2192 GrayBox (being broken down or worked on) \u2192 WhiteBox (done).\n\n### What to do every time you work\n\n1. **Read first**: read `npx boxglow resume` (or `npx boxglow status --brief`) for work in progress, pending decisions, unread answers, next candidates, and handoffs for unfinished boxes. To continue a previous session, start from the handoff notes shown there. Check the inputs and outputs of your box with `npx boxglow show <block>`\n2. **Record the start**: when you take on a box, run `npx boxglow start <block> --note "<what you will do>"`. Work on at most 1-2 boxes at a time.\n On a plan with the context guard on, first read `npx boxglow context <block>` and pass the `contextToken` (context token) from its output:\n `npx boxglow start <block> --note "<what you will do>" --context-token <context token>` (see "Context token and handoff" below)\n3. **Split**: when you cannot see how to produce the output with a single box, place smaller boxes inside it with `npx boxglow split <block> --spec \'<JSON>\'`. Always decide the output (deliverable) of each box. Inputs can be omitted if unknown (they automatically become inputs of the level above)\n4. **Record completion**: when the output\'s deliverable exists, run `npx boxglow done <block> --artifact "<name>=<path in the repository>"`.\n With the guard on, add `--context-token <context token>` (if your previous command printed "New context token: <token>", use that one).\n An unchanged file already committed to HEAD is recorded as "commit + path + content hash" (nothing is uploaded). Uncommitted or untracked files remain local file references, and `npx boxglow check` turns them into Git references once they are committed; do not create a commit solely to satisfy Boxglow. Use full `npx boxglow status` when you need the entire tree. For PRs and external material, a URL is fine.\n To attach a deliverable ahead of completion, use `npx boxglow artifact <block> <path>`. Record progress along the way with `npx boxglow set <block> --progress 60`\n5. **When a human decision is needed**: `npx boxglow ask <block> "<question>" --options "A|B"`. Do not proceed with that box until it is answered; move on to another box. A human\'s answer appears under "Answered" in `status`. After reading it, acknowledge it with `npx boxglow ack <block>` (`start` / `done` / `set` etc. on that box also acknowledge it automatically). Until you acknowledge it, it stays on the human\'s screen as "Not read by the AI yet" and the human can still revise the answer. If the answer contains a question back to you, record your own answer too with `ask` and `answer --by <yourself>`\n6. **When you are stuck**: `npx boxglow blocked <block> --note "<what is in the way>"`\n7. **Check deliverables**: after moving or renaming files, run `npx boxglow check` (it detects the move and updates the path; if the file cannot be found, it is flagged)\n8. **Before an interruption or a handoff**: before the session ends, the context is compacted, or you hand over to another AI or a person, save a handoff note with `npx boxglow checkpoint <block> --note "Findings; next steps; unresolved questions"` (with the guard on, add `--context-token <context token>`). It stays in the plan even when the chat history is gone, and the next session reads it with `resume` and `context`\n9. **Report**: at the end of your work, summarize `npx boxglow status --brief` and report it\n\n### Structural rules\n\n- The top level contains only the "input node", the "final deliverable node" and "project boxes". Tasks go inside a project box (the default parent of `add` is the first project box)\n- There is no limit on depth. `split` can be used at any level\n- A box that could be reused in another project (e.g. converting input images to monochrome) can be turned into a template with `npx boxglow export-block <block> --out <name>.boxglow-block.json` and\n inserted with `npx boxglow import-block <path> --parent <block>`. You may share templates by putting them in the repository\'s `boxglow-blocks/`\n\nWhen there are many top-level inputs, create a group with `npx boxglow group "<group name>"` and sort inputs into it with `npx boxglow group-set <input name> <group name>` (e.g. PCIe spec / DDR spec).\n\n### JSON format for split\n\n```json\n{\n "blocks": [\n { "title": "Design", "description": "What to do", "inputs": ["Spec"], "outputs": ["Design doc"] },\n { "title": "Build", "inputs": ["Design doc"], "outputs": ["Code"] }\n ],\n "connections": [\n { "from": "parent.Spec", "to": "Design.Spec" },\n { "from": "Design.Design doc", "to": "Build.Design doc" },\n { "from": "Build.Code", "to": "parent.Output" }\n ]\n}\n```\n\n`parent.<name>` is an input / output of the box being split. `<block>` is a short ID (such as B12; shown in status) or a title.\nA box with no children has a single output (write just one entry in outputs). Make the title a short phrase that tells "what is made".\nRecord dates and hours with `npx boxglow set <block> --due 2026-10-15 --start 2026-10-01 --estimate 8 --hours 3.5`.\nGive each box a kind of work (category): `--category design` / build / verify / evaluate / study / research / ui / improve / fix / docs / ops / other (other: when unsure). Set it at add time; to change it, use `npx boxglow set <block> --category verify`.\nThe receiving side of a connection can be just a title (`npx boxglow connect "A.Design doc" "B"` creates an input "Design doc" on B and connects it. In split\'s connections, `to: "B"` also works). A connected input takes the name of the source output and cannot be renamed on the input side (to change it, rename the source output; everything connected to it changes with it). To add inputs / outputs to an existing box or rename them, use `npx boxglow port <block> --in <name> --out <name> --rename <old>=<new>` (`project` = the first project box. This also renames the final deliverable). Remove a wire with `npx boxglow disconnect <title.output name> <title.input name>`, and remove a box itself with `npx boxglow remove <block>` (`--force` if it has boxes inside). The MCP tools (`boxglow_status` / `boxglow_start` / `boxglow_done` / `boxglow_ask` etc.; available after registration in your agent; `.mcp.json` is the Claude Code configuration) are another entry point to the same commands, and either way updates the same file. Export the plan as a document with `npx boxglow export --out docs/ROADMAP.md`.\nIn a new project, first create 3-7 top-level items from the direction (the README or the request), confirm them with a human via `ask`, and then `split` only the item you are about to start (leave later stages coarse). Write each `ask` question so that it can be decided on its own, and put the premises, comparison and impact in `--context` (do not point outside the question, as in "Is this OK?"). When choosing one of several candidates, even if you decide by yourself, record it with `ask ... --options "A|B|C"` and `answer --by <yourself>` so that the candidates you did not choose are kept. To change direction, use `reopen <block> --note <reason>`. Everything works with the CLI alone (the screen is for humans to watch). If no human is in the loop, the AI records its decisions with `answer --by <yourself>` and proceeds.\nName each output after a "concrete deliverable" (e.g. `Design doc docs/design.md`, `PR #12`, `Public URL`, `Test results (vitest, 53 tests)`). Avoid abstract names such as "feature set" or "findings", and always attach a file, URL or commit with --artifact when you run done.\nMake sure every box\'s "input \u2192 output" is connected to something (an unconnected box is a hole in the plan. Unconnected inputs are raised to the top-level input node automatically).\nIf several people edit the same boxglow.json, each of them runs `npx boxglow git-setup` once in their own clone (changes are merged automatically per box, and changes to different boxes do not conflict).\nTo manage several repositories with one plan, put boxglow.json in the folder above them and set the environment variable BOXGLOW_FILE to its location in each repository (one project box per repository: `npx boxglow project "name" --repo <path>`).\nInputs are required by default. On the screen, mark an input as "Optional" if work can start without it. "Next candidates" in status lists boxes whose required inputs are ready first (each is marked "ready to start" or "waiting for required inputs").\nDo not write descriptions for inputs (the description of the connected output is used). Write the format and constraints in the output\'s description.\n\n\n### Context token and handoff\n\nThe context guard keeps an AI from continuing work on outdated instructions. `setup-agent` turns it on (on other plans, run `npx boxglow guard on`).\nWhile it is on, an AI\'s `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` need `--context-token <context token>`. Commands run by a person (`--actor human`) do not.\n\n```\nnpx boxglow context B12 # read the decisions (including acknowledged answers), input/output contracts and handoff notes. contextToken in the output is the context token\nnpx boxglow start B12 --note "<what you will do>" --context-token <context token>\nnpx boxglow ask B12 "<question>" --options "A|B" --context-token <context token>\n# -> last line of the output: New context token: <token>\nnpx boxglow done B12 --artifact "<name>=<path>" --context-token <new context token>\n```\n\n- **The context token changes (you must reread)** when a person answers a decision or revises an answer, a person changes the instructions (title, description, input/output contracts), or another AI or a person updates the handoff note. The same applies when this happens on a parent box or an input provider. The old context token is rejected: reread `context`, take the change into account in your work, then retry\n- **The context token does not change** on `start` / `blocked` / `review` / `leave` / `ack`, on changes to progress, status, dates or category, when `check` verifies deliverables, or when boxes are moved on the screen. Keep using the same context token\n- **You can update it yourself**: when your own `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` etc. changes the context, the last line of its output is "New context token: <token>". You already know what you changed, so use it for the next command without rereading. `ask` / `decision` / `answer` / `reopen` run without a context token, but they print the new context token only when you pass the latest one\n- Do not turn the guard off to get past an old context token (an AI needs the latest context token to run `guard off`)\n- With MCP, use `boxglow_context` and the `contextToken` argument of each tool (the new context token is the last line of the tool result)\n- If "Another Boxglow writer holds the file lock" persists after waiting and retrying, show the output of `npx boxglow unlock` (owner and verdict) to a person and ask them to remove it. Never add `--actor human` yourself or delete the lock folder directly\n\n### Readiness, current scope, and fresh resume information\n\n- Read current activity, pending decisions, and unread answers first. Candidates are grouped by current scope / other candidates, then ready / waiting for inputs, with missing input names. A waiting candidate is not ready.\n- Four optional scope fields are available: goal, non-goals, acceptance, and consult (when to consult before expanding scope). Read the target and parent scopes at the start of context before starting and finishing. Textual acceptance criteria are not automatically evaluated.\n- Use scope B12 --goal "Goal" --non-goals "Excluded" --acceptance "Checks" --consult "When to ask". scope B12 only reads; none or an empty string clears a field. In the inspector, use \u22EF \u2192 Edit work scope. Only populated fields appear in the collapsed scope section.\n- focus B12 prioritizes that box and its descendants; focus reads the selection, and focus none clears it. Other candidates remain in a separate group. Being listed does not authorize expanding scope or publishing.\n- Missing required inputs warn on start by default but still allow the start. Supply --reason "Why starting early is appropriate" to proceed without a warning and save the reason in both activity and the log. Artifact-free done also remains warning-only by default. Existing output artifacts or --artifact count; reference material on the box does not.\n- Only enable strict checks when selected for the plan: policy --start reject --done reject. Each can return to warn. A recorded start reason also overrides reject. The artifact requirement also applies to set --status white. Human operations (UI / --actor human) are never rejected. Do not impersonate a human or weaken policy to bypass rejection.\n- With guard enabled, scope writes, focus selection/clearing, and policy writes require --context-token. These changes print a new token: use it next. Read-only forms need no token.\n- resume hides completed handoff bodies and shows their count. Request resume --include-completed only when history is needed; context / show can still read individual notes.\n- descriptionUpdatedAt dates description changes; statusChangedAt dates status changes; handoffs use their existing at field. Notes predating a status change are marked. Unknown legacy dates stay unknown, and recency does not establish truth.\n- After Done, CLI, inspector, and resume flag descriptions that may still describe an earlier state. Text is never rewritten automatically. Review it, then update with set B12 --note "Current description" (none clears it) or Notes in the UI.\n- MCP: boxglow_scope / boxglow_focus / boxglow_policy, reason on boxglow_start, and includeCompleted on boxglow_resume. Pass contextToken for writes.\n- All data fields are optional; schema v5 is unchanged. Old versions (0.4.2 and earlier) do not enforce these checks and drop workflowPolicy / focusBlockId when saving. Align all writers (CLI, serve, web app, bundled extension app) to a supporting build before using these settings. Check build time as well as version.\n\n---\n';
|
|
3435
|
-
|
|
3436
|
-
// .claude/skills/boxglow/SKILL.md
|
|
3437
|
-
var SKILL_default = '---\nname: boxglow\ndescription: Boxglow (boxglow.json) \u3067\u8A08\u753B\u3068\u9032\u6357\u3092\u4EBA\u9593\u3068\u5171\u6709\u3059\u308B\u3002\u4F5C\u696D\u306E\u958B\u59CB\u30FB\u5206\u89E3\u30FB\u5B8C\u4E86\u30FB\u5224\u65AD\u5F85\u3061\u3092 npx boxglow \u3067\u8A18\u9332\u3059\u308B\u3002\u300C\u9032\u6357\u3092 Boxglow \u306B\u8A18\u9332\u3057\u3066\u300D\u300Cboxglow \u3067\u72B6\u6CC1\u3092\u898B\u3066\u300D\u300C/boxglow\u300D\u3067\u4F7F\u7528\u3002\n---\n\n# Boxglow \u3067\u8A08\u753B\u3068\u9032\u6357\u3092\u5171\u6709\u3059\u308B\n\n\u30EA\u30DD\u30B8\u30C8\u30EA\u76F4\u4E0B\u306E `boxglow.json` \u304C\u8A08\u753B\u306E\u6B63\u672C\u3002\u4EBA\u9593\u306F Boxglow \u306E\u753B\u9762 (https://boxglow.pages.dev/ \u3067\u300Cboxglow.json \u3092\u958B\u304F\u300D) \u3067\u898B\u3066\u3044\u308B\u3002\n\u76F4\u63A5\u7DE8\u96C6\u305B\u305A `npx boxglow` \u3067\u66F4\u65B0\u3059\u308B\u3002\n\n## \u6700\u521D\u306E\u8A08\u753B\u3065\u304F\u308A (\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u3068\u304D)\n\n1. \u65B9\u91DD (README\u3001\u4F9D\u983C\u6587\u3001\u8981\u4EF6\u30E1\u30E2) \u3092\u8AAD\u307F\u3001`npx boxglow init --name "<\u540D\u524D>"` \u306E\u3042\u3068\u3001\u6700\u4E0A\u4F4D\u306B\u5927\u9805\u76EE (3\u301C7 \u500B) \u3092 `add` \u3067\u7F6E\u304F\u3002\n \u5404\u5927\u9805\u76EE\u306B\u306F\u300C\u5165\u529B (\u4F55\u3092\u4F7F\u3046\u304B)\u300D\u3068\u300C\u51FA\u529B (\u5177\u4F53\u7684\u306A\u6210\u679C\u7269: \u30D5\u30A1\u30A4\u30EB\u30FBURL\u30FBPR)\u300D\u3092\u5FC5\u305A\u66F8\u304D\u3001\u6700\u7D42\u6210\u679C\u7269\u307E\u3067\u7DDA\u3067\u3064\u306A\u3050 (`connect`)\n2. \u4F5C\u3063\u305F\u3089 `npx boxglow ask <\u6700\u521D\u306E\u5927\u9805\u76EE> "\u3053\u306E\u5927\u9805\u76EE\u306E\u5206\u3051\u65B9\u3067\u3088\u3044\u3067\u3059\u304B?" --options "\u3088\u3044|\u76F4\u3057\u305F\u3044"` \u3067\u4EBA\u306E\u78BA\u8A8D\u3092\u53D6\u308B\u3002\n \u4EBA\u306F\u753B\u9762\u3067\u3082\u3001`npx boxglow answer <block> "<\u56DE\u7B54>"` \u3067\u3082\u3001\u30C1\u30E3\u30C3\u30C8\u3067\u3082\u7B54\u3048\u3089\u308C\u308B\u3002\u30C1\u30E3\u30C3\u30C8\u3067\u7B54\u3048\u304C\u8FD4\u3063\u305F\u3089 AI \u304C `answer --by human` \u3067\u8A18\u9332\u3059\u308B\u3002\n \u4EBA\u304C\u753B\u9762\u3092\u4F7F\u308F\u306A\u3044\u904B\u7528 (AI \u3060\u3051\u3067\u9032\u3081\u308B\u6307\u793A\u304C\u3042\u308B\u3068\u304D) \u306F\u3001AI \u81EA\u8EAB\u304C `answer --by claude-code "<\u5224\u65AD\u3068\u7406\u7531>"` \u3067\u8A18\u9332\u3057\u3066\u5148\u3078\u9032\u3080\u3002\u5224\u65AD\u306E\u8A18\u9332\u3092\u6B8B\u3059\u3053\u3068\u304C\u76EE\u7684\u3067\u3001\u753B\u9762\u306F\u5FC5\u9808\u3067\u306F\u306A\u3044\n3. \u7740\u624B\u3059\u308B\u5927\u9805\u76EE\u3060\u3051\u3092 `split` \u3067\u5206\u89E3\u3059\u308B (\u5148\u306E\u6BB5\u968E\u306F\u7C97\u3044\u307E\u307E\u3002\u9032\u3081\u306A\u304C\u3089\u7D30\u304B\u304F\u3059\u308B)\n\n## \u624B\u9806\n\n1. `npx boxglow resume` (\u307E\u305F\u306F `npx boxglow status --brief`) \u3092\u8AAD\u3080\u3002\u4F5C\u696D\u4E2D\u306E\u30DC\u30C3\u30AF\u30B9\u3001\u5224\u65AD\u5F85\u3061\u3001AI \u672A\u78BA\u8A8D\u306E\u56DE\u7B54\u3001\u6B21\u306E\u5019\u88DC\u3001\u672A\u5B8C\u4E86\u306E\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u628A\u63E1\u3059\u308B\u3002\u5168\u968E\u5C64\u304C\u8981\u308B\u3068\u304D\u306F `npx boxglow status`\n2. \u62C5\u5F53\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u3092\u6C7A\u3081\u305F\u3089 `npx boxglow show <block>` \u3067\u5165\u51FA\u529B\u3092\u78BA\u8A8D\u3057\u3001`npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>"`\u3002\n \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u304C\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001\u5148\u306B `npx boxglow context <block>` \u3092\u8AAD\u307F\u3001\u51FA\u529B\u306E `contextToken` (\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3) \u3092 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3067\u4ED8\u3051\u308B\n3. \u5927\u304D\u3044\u30DC\u30C3\u30AF\u30B9\u306F `npx boxglow split <block> --spec \'<JSON>\'` \u3067\u5206\u89E3\u3059\u308B (\u5F62\u5F0F\u306F `npx boxglow help`)\u3002\u5404\u30DC\u30C3\u30AF\u30B9\u306E\u51FA\u529B\u3092\u5FC5\u305A\u6C7A\u3081\u308B\n4. \u6210\u679C\u7269\u304C\u3067\u304D\u305F\u3089 `npx boxglow done <block> --artifact "<\u540D\u524D>=<URL \u307E\u305F\u306F\u30D1\u30B9>"` (guard \u304C\u6709\u52B9\u306A\u3089 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3092\u4ED8\u3051\u308B\u3002\u76F4\u524D\u306E\u81EA\u5206\u306E\u64CD\u4F5C\u306E\u51FA\u529B\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u3066\u3044\u308C\u3070\u3001\u305D\u308C\u3092\u4F7F\u3046)\n5. \u4EBA\u9593\u306E\u5224\u65AD\u304C\u8981\u308B\u3068\u304D\u306F `npx boxglow ask <block> "<\u8CEA\u554F>" --options "A|B"` \u3057\u3066\u4ED6\u306E\u30DC\u30C3\u30AF\u30B9\u3078\u79FB\u308B\n6. \u8A70\u307E\u3063\u305F\u3089 `npx boxglow blocked <block> --note "<\u56F0\u3063\u3066\u3044\u308B\u3053\u3068>"`\n7. \u4E2D\u65AD\u30FB\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u5727\u7E2E\u30FB\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B `npx boxglow checkpoint <block> --note "\u5206\u304B\u3063\u305F\u3053\u3068; \u6B21\u306B\u3059\u308B\u3053\u3068; \u672A\u89E3\u6C7A\u306E\u3053\u3068"` \u3067\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u6B8B\u3059\n8. \u6700\u5F8C\u306B `npx boxglow status --brief` \u3092\u8981\u7D04\u3057\u3066\u5831\u544A\u3059\u308B\n\n## \u7D04\u675F\n\n- `ask` \u306E\u8CEA\u554F\u306F\u3001\u305D\u308C\u3060\u3051\u3067\u5224\u65AD\u3067\u304D\u308B\u3088\u3046\u306B\u66F8\u304F\u3002\u300C\u3053\u308C\u3067\u3088\u3044\u3067\u3059\u304B\u300D\u300C\u4E0A\u8A18\u306E\u6848\u300D\u306E\u3088\u3046\u306B\u5916\u3092\u6307\u3055\u305A\u3001\u524D\u63D0\u30FB\u6BD4\u8F03\u30FB\u5F71\u97FF\u3092 `--context` \u306B\u66F8\u304F (\u4EBA\u306F\u4E00\u89A7\u306E\u4E2D\u306E\u8CEA\u554F\u3060\u3051\u3092\u8AAD\u3093\u3067\u7B54\u3048\u308B)\n- \u5019\u88DC\u304B\u3089 1 \u3064\u3092\u9078\u3076\u5834\u9762 (\u65B9\u5F0F\u30FB\u30E9\u30A4\u30D6\u30E9\u30EA\u30FB\u8A2D\u8A08\u306E\u9078\u629E) \u3067\u306F\u3001AI \u304C\u81EA\u5206\u3067\u6C7A\u3081\u308B\u3068\u304D\u3082 `ask <block> "<\u554F\u3044>" --options "A|B|C"` \u2192 `answer --by claude-code "<\u9078\u3093\u3060\u3082\u306E>\u3002\u7406\u7531: ..."` \u306E\u5F62\u3067\u8A18\u9332\u3057\u3001\u9078\u3070\u306A\u304B\u3063\u305F\u5019\u88DC\u3092\u30DC\u30C3\u30AF\u30B9\u306B\u6B8B\u3059 (\u65B9\u91DD\u8EE2\u63DB\u306E\u3068\u304D\u306B\u623B\u308C\u308B)\n- \u65B9\u91DD\u8EE2\u63DB\u306F `reopen <block> --note "<\u7406\u7531>"` \u3067\u524D\u306E\u7B54\u3048\u3092\u5C65\u6B74\u306B\u6B8B\u3057\u3066\u9078\u3073\u76F4\u3059 (\u5019\u88DC\u306F\u6D88\u3055\u306A\u3044)\n\n- \u540C\u6642\u306B\u4F5C\u696D\u4E2D\u306B\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u306F 1\u301C2 \u500B\u307E\u3067\n- \u6210\u679C\u7269 (artifact) \u306F\u5F8C\u304B\u3089\u4EBA\u304C\u958B\u3051\u308B\u3082\u306E (\u30B3\u30DF\u30C3\u30C8\u3001PR\u3001\u30D5\u30A1\u30A4\u30EB\u306E\u30D1\u30B9) \u306B\u3059\u308B\n- \u51FA\u529B\u306F\u5177\u4F53\u7684\u306A\u6210\u679C\u7269\u306E\u540D\u524D\u306B\u3059\u308B (\u30D5\u30A1\u30A4\u30EB\u30FBPR\u30FBURL\u30FB\u30C6\u30B9\u30C8\u7D50\u679C)\u3002\u62BD\u8C61\u7684\u306A\u540D\u524D (\u6A5F\u80FD\u4E00\u5F0F\u3001\u6240\u898B) \u306F\u907F\u3051\u308B\n- \u7D50\u7DDA\u306E\u53D7\u3051\u5074\u306F\u984C\u540D\u3060\u3051\u3067\u3088\u3044 (`connect "A.\u8A2D\u8A08\u66F8" "B"` \u3067 B \u306B\u5165\u529B\u300C\u8A2D\u8A08\u66F8\u300D\u304C\u4F5C\u3089\u308C\u308B)\u3002\u5165\u529B\u540D\u306F\u4F9B\u7D66\u5143\u306E\u51FA\u529B\u540D\u3067\u6C7A\u307E\u308A\u3001\u5165\u529B\u5074\u3067\u306F\u5909\u3048\u306A\u3044\n- \u30DC\u30C3\u30AF\u30B9\u306F\u5FC5\u305A\u300C\u5165\u529B \u2192 \u51FA\u529B\u300D\u3067\u3064\u306A\u3050\u3002\u8DB3\u308A\u306A\u3044\u5165\u51FA\u529B\u306F `port`\u3001\u5916\u3059\u306E\u306F `disconnect`\u3001\u30DC\u30C3\u30AF\u30B9\u305D\u306E\u3082\u306E\u3092\u6D88\u3059\u306E\u306F `remove`\u3002\u6587\u66F8\u5316\u306F `export --out docs/ROADMAP.md`\n- \u30DC\u30C3\u30AF\u30B9\u3092 add \u3059\u308B\u3068\u304D\u306F\u4ED5\u4E8B\u306E\u7A2E\u985E\u3092 `--category` \u3067\u4ED8\u3051\u308B (design \u8A2D\u8A08 / build \u5B9F\u88C5 / verify \u691C\u8A3C / evaluate \u8A55\u4FA1 / study \u691C\u8A0E / research \u8ABF\u67FB / ui \u30C7\u30B6\u30A4\u30F3 / improve \u6539\u5584 / fix \u8AB2\u984C\u89E3\u6C7A / docs \u6587\u66F8 / ops \u904B\u7528 / other \u305D\u306E\u4ED6)\u3002\u753B\u9762\u3067\u306F\u8272\u306E\u5E2F\u3068\u672D\u306B\u306A\u308B\n- boxglow.json \u304C\u307E\u3060\u7121\u3051\u308C\u3070 `npx boxglow init --name "<\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D>"` \u3067\u4F5C\u308A\u3001\u6700\u7D42\u6210\u679C\u7269\u3068\u6700\u4E0A\u4F4D\u306E\u30DC\u30C3\u30AF\u30B9\u3092 `add` \u3067\u7F6E\u304F\n\n## \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3068\u5F15\u304D\u7D99\u304E\n\n\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u306F\u3001AI \u304C\u53E4\u3044\u6307\u793A\u306E\u307E\u307E\u4F5C\u696D\u3092\u9032\u3081\u308B\u306E\u3092\u9632\u3050\u4ED5\u7D44\u307F\u3002`setup-agent` \u304C\u6709\u52B9\u306B\u3059\u308B (\u305D\u308C\u4EE5\u5916\u306E\u8A08\u753B\u3067\u306F `npx boxglow guard on`)\u3002\n\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001AI \u306E `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` \u306B `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u304C\u8981\u308B\u3002\u4EBA\u306E\u64CD\u4F5C (`--actor human`) \u306B\u306F\u8981\u6C42\u3055\u308C\u306A\u3044\u3002\n\n```\nnpx boxglow context B12 # \u5224\u65AD (\u5F15\u304D\u53D6\u308A\u6E08\u307F\u306E\u56DE\u7B54\u3092\u542B\u3080)\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u8AAD\u3080\u3002\u51FA\u529B\u306E contextToken \u304C\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\nnpx boxglow start B12 --note "<\u4F55\u3092\u3059\u308B\u304B>" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\nnpx boxglow ask B12 "<\u8CEA\u554F>" --options "A|B" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n# \u2192 \u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C: \u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\nnpx boxglow done B12 --artifact "<\u540D\u524D>=<\u30D1\u30B9>" --context-token <\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n```\n\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u308B\u5834\u5408 (\u8AAD\u307F\u76F4\u3057\u304C\u8981\u308B)**: \u4EBA\u304C\u56DE\u7B54\u3057\u305F\u30FB\u56DE\u7B54\u3092\u76F4\u3057\u305F\u3001\u4EBA\u304C\u6307\u793A (\u984C\u540D\u30FB\u8AAC\u660E\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6) \u3092\u5909\u3048\u305F\u3001\u4ED6\u306E AI \u3084\u4EBA\u304C\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u66F4\u65B0\u3057\u305F\u3002\u89AA\u3084\u5165\u529B\u5143\u306E\u30DC\u30C3\u30AF\u30B9\u3067\u8D77\u304D\u305F\u5834\u5408\u3082\u540C\u3058\u3002\u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u62D2\u5426\u3055\u308C\u308B\u306E\u3067\u3001`context` \u3092\u8AAD\u307F\u76F4\u3057\u3001\u5909\u308F\u3063\u305F\u5185\u5BB9\u3092\u4F5C\u696D\u306B\u53CD\u6620\u3057\u3066\u304B\u3089\u3084\u308A\u76F4\u3059\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u3089\u306A\u3044\u5834\u5408**: `start` / `blocked` / `review` / `leave` / `ack`\u3001\u9032\u6357\u30FB\u72B6\u614B\u30FB\u671F\u65E5\u30FB\u30AB\u30C6\u30B4\u30EA\u306E\u5909\u66F4\u3001`check` \u306B\u3088\u308B\u6210\u679C\u7269\u306E\u78BA\u8A8D\u3001\u753B\u9762\u3067\u306E\u914D\u7F6E\u306E\u5909\u66F4\u3002\u540C\u3058\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u3044\u7D9A\u3051\u3089\u308C\u308B\n- **\u81EA\u5206\u3067\u66F4\u65B0\u3067\u304D\u308B\u5834\u5408**: \u81EA\u5206\u306E `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` \u306A\u3069\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5909\u308F\u3063\u305F\u3068\u304D\u306F\u3001\u305D\u306E\u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u308B\u3002\u8AAD\u307F\u76F4\u3055\u305A\u306B\u6B21\u306E\u64CD\u4F5C\u3078\u4F7F\u3046\u3002`ask` / `decision` / `answer` / `reopen` \u306F\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u7121\u3057\u3067\u3082\u5B9F\u884C\u3067\u304D\u308B\u304C\u3001\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4ED8\u3051\u305F\u3068\u304D\u3060\u3051\n- \u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u901A\u3059\u305F\u3081\u306B guard \u3092\u7121\u52B9\u306B\u3057\u306A\u3044 (AI \u304C `guard off` \u3059\u308B\u306B\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u8981\u308B)\n- \u518D\u958B\u3059\u308B\u3068\u304D\u306F `npx boxglow resume` \u3092\u8AAD\u307F\u3001\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u306E\u3042\u308B\u30DC\u30C3\u30AF\u30B9\u306F `context` \u3067\u4E2D\u8EAB\u3092\u78BA\u304B\u3081\u3066\u304B\u3089\u7D9A\u3051\u308B\u3002\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u306F\u4F1A\u8A71\u306E\u5C65\u6B74\u3084\u6D3B\u52D5\u30ED\u30B0\u306E\u4E0A\u9650\u3068\u306F\u5225\u306B\u3001\u8A08\u753B\u306E\u4E2D\u306B\u6B8B\u308B\n- MCP \u3067\u306F `boxglow_context` \u3068\u3001\u5404\u30C4\u30FC\u30EB\u306E `contextToken` \u5F15\u6570\u3092\u4F7F\u3046\n- \u300C\u4ED6\u306E Boxglow \u304C\u66F8\u304D\u8FBC\u307F\u4E2D (\u30ED\u30C3\u30AF\u4E2D)\u300D\u3068\u51FA\u3066\u3001\u5C11\u3057\u5F85\u3063\u3066\u3084\u308A\u76F4\u3057\u3066\u3082\u89E3\u3051\u306A\u3044\u3068\u304D\u306F\u3001`npx boxglow unlock` \u306E\u8868\u793A (\u6301\u3061\u4E3B\u3068\u5224\u5B9A) \u3092\u4EBA\u306B\u898B\u305B\u3066\u89E3\u9664\u3092\u983C\u3080\u3002\u81EA\u5206\u3067 `--actor human` \u3092\u4ED8\u3051\u305F\u308A\u3001\u30ED\u30C3\u30AF\u306E\u30D5\u30A9\u30EB\u30C0\u3092\u76F4\u63A5\u6D88\u3057\u305F\u308A\u3057\u306A\u3044\n\n\n### \u7740\u624B\u30FB\u5B8C\u4E86\u3001\u4ECA\u56DE\u306E\u7BC4\u56F2\u3001\u8A18\u9332\u306E\u9BAE\u5EA6\n\n- \u6700\u521D\u306B resume \u306E\u73FE\u6CC1 (\u6D3B\u52D5\u30FB\u5224\u65AD\u5F85\u3061\u30FB\u672A\u78BA\u8A8D\u56DE\u7B54) \u3092\u8AAD\u3080\u3002\u5019\u88DC\u306F\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2 / \u305D\u306E\u4ED6\u300D\u306E\u5404\u7D44\u3067\u300C\u7740\u624B\u3067\u304D\u308B / \u5165\u529B\u5F85\u3061\u300D\u306B\u5206\u304B\u308C\u3001\u4E0D\u8DB3\u3059\u308B\u5165\u529B\u540D\u304C\u51FA\u308B\u3002\u5165\u529B\u5F85\u3061\u3092\u3001\u6E96\u5099\u6E08\u307F\u3068\u53D6\u308A\u9055\u3048\u306A\u3044\u3002\n- \u30DC\u30C3\u30AF\u30B9\u306E\u4EFB\u610F4\u9805\u76EE\u3092 scope \u3067\u8A2D\u5B9A\u3059\u308B\u3002goal = \u4ECA\u56DE\u9054\u6210\u3059\u308B\u3053\u3068\u3001non-goals = \u4ECA\u56DE\u306F\u6271\u308F\u306A\u3044\u3053\u3068\u3001acceptance = \u5B8C\u4E86\u3068\u5224\u65AD\u3059\u308B\u6761\u4EF6\u3001consult = \u7BC4\u56F2\u3092\u5E83\u3052\u308B\u524D\u306B\u76F8\u8AC7\u3059\u308B\u6761\u4EF6\u3002context \u306E\u5148\u982D\u3067\u5BFE\u8C61\u3068\u89AA\u306E\u7BC4\u56F2\u3092\u8AAD\u307F\u3001\u7740\u624B\u524D\u3068\u5B8C\u4E86\u524D\u306B\u7167\u5408\u3059\u308B\u3002\u5B8C\u4E86\u6761\u4EF6\u306E\u672C\u6587\u3092AI\u304C\u6E80\u305F\u3057\u305F\u304B\u306F\u81EA\u52D5\u5224\u5B9A\u3057\u306A\u3044\u3002\n- scope B12 --goal "\u9054\u6210\u3059\u308B\u3053\u3068" --non-goals "\u4ECA\u56DE\u6271\u308F\u306A\u3044\u3053\u3068" --acceptance "\u78BA\u8A8D\u3059\u308B\u6761\u4EF6" --consult "\u76F8\u8AC7\u3059\u308B\u6761\u4EF6" \u3067\u8A2D\u5B9A\u3059\u308B\u3002scope B12 \u306F\u8868\u793A\u3060\u3051\u3002\u5404\u9805\u76EE\u306B none \u307E\u305F\u306F\u7A7A\u6587\u5B57\u3092\u6E21\u3059\u3068\u6D88\u3059\u3002\u753B\u9762\u3067\u306F\u8A73\u7D30\u30D1\u30CD\u30EB\u306E \u22EF \u2192\u300C\u4F5C\u696D\u7BC4\u56F2\u3092\u7DE8\u96C6\u300D\u3002\u8A18\u5165\u6E08\u307F\u306E\u9805\u76EE\u3060\u3051\u304C\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2\u300D\u306B\u51FA\u308B\u3002\n- focus B12 \u3067\u3053\u306E\u30DC\u30C3\u30AF\u30B9\u3068\u914D\u4E0B\u3092\u6B21\u5019\u88DC\u306E\u5148\u982D\u306B\u3059\u308B\u3002focus \u306F\u73FE\u5728\u306E\u5BFE\u8C61\u306E\u8868\u793A\u3001focus none \u306F\u89E3\u9664\u3002\u5BFE\u8C61\u5916\u3082\u5225\u306E\u7D44\u306B\u6B8B\u308B\u3002\u5019\u88DC\u306B\u51FA\u308B\u3053\u3068\u81EA\u4F53\u306F\u4F5C\u696D\u7BC4\u56F2\u306E\u62E1\u5927\u3084\u516C\u958B\u3092\u8A31\u53EF\u3057\u306A\u3044\u3002\n- start \u306E\u5FC5\u9808\u5165\u529B\u4E0D\u8DB3\u306F\u65E2\u5B9A\u3067\u8B66\u544A\u3059\u308B\u304C\u3001\u5F93\u6765\u3069\u304A\u308A\u958B\u59CB\u3067\u304D\u308B\u3002\u5148\u884C\u4F5C\u696D\u304C\u5FC5\u8981\u306A\u3089 --reason "\u5148\u306B\u884C\u3046\u7406\u7531" \u3092\u4ED8\u3051\u308B\u3002\u8B66\u544A\u306F\u51FA\u305A\u3001\u7406\u7531\u306F\u6D3B\u52D5\u3068\u30ED\u30B0\u306B\u6B8B\u308B\u3002done \u306E\u51FA\u529B\u6210\u679C\u7269\u306A\u3057\u3082\u65E2\u5B9A\u3067\u8B66\u544A\u306E\u307F\u3002\u65E2\u5B58\u306E\u51FA\u529B\u6210\u679C\u7269\u307E\u305F\u306F --artifact \u3092\u6570\u3048\u3001\u30DC\u30C3\u30AF\u30B9\u306E\u53C2\u8003\u8CC7\u6599\u306F\u6570\u3048\u306A\u3044\u3002\n- \u8A08\u753B\u3067\u9078\u3076\u5834\u5408\u3060\u3051 policy --start reject --done reject \u306B\u3059\u308B\u3002\u305D\u308C\u305E\u308C warn \u306B\u623B\u305B\u308B\u3002\u5165\u529B\u5F85\u3061\u306E\u62D2\u5426\u3067\u3082\u7406\u7531\u4ED8\u304D\u306E\u958B\u59CB\u306F\u53EF\u80FD\u3002\u6210\u679C\u7269\u306A\u3057\u306E\u62D2\u5426\u306F set --status white \u306B\u3082\u52B9\u304F\u3002\u4EBA\u306E\u64CD\u4F5C (\u753B\u9762 / --actor human) \u306F\u62D2\u5426\u3057\u306A\u3044\u3002AI\u304C\u62D2\u5426\u3092\u907F\u3051\u308B\u305F\u3081\u306B\u4EBA\u3092\u540D\u4E57\u3063\u305F\u308A\u3001\u8A2D\u5B9A\u3092\u5F31\u3081\u305F\u308A\u3057\u306A\u3044\u3002\n- guard \u6709\u52B9\u6642\u3001scope \u306E\u5909\u66F4\u3001focus \u306E\u8A2D\u5B9A\u30FB\u89E3\u9664\u3001policy \u306E\u5909\u66F4\u306B\u3082 --context-token \u304C\u5FC5\u8981\u3002scope/focus/policy \u3092\u5909\u3048\u308B\u3068\u65B0\u3057\u3044\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u3067\u3001\u6B21\u306E\u64CD\u4F5C\u306F\u305D\u308C\u3092\u4F7F\u3046\u3002\u8868\u793A\u3060\u3051\u306B\u306F\u4E0D\u8981\u3002\n- resume \u306F\u5B8C\u4E86\u6E08\u307F\u306E\u5F15\u304D\u7D99\u304E\u672C\u6587\u3092\u96A0\u3057\u3001\u4EF6\u6570\u3060\u3051\u793A\u3059\u3002\u5C65\u6B74\u304C\u5FC5\u8981\u306A\u5834\u5408\u3060\u3051 resume --include-completed \u3067\u5C55\u958B\u3059\u308B (context / show \u3067\u306F\u500B\u5225\u306B\u8AAD\u3081\u308B)\u3002\n- \u8AAC\u660E\u306F descriptionUpdatedAt\u3001\u72B6\u614B\u306F statusChangedAt\u3001\u5F15\u304D\u7D99\u304E\u306F\u65E2\u5B58\u306E at \u3067\u65E5\u6642\u3092\u78BA\u8A8D\u3067\u304D\u308B\u3002\u72B6\u614B\u5909\u66F4\u3088\u308A\u53E4\u3044\u30E1\u30E2\u306F\u305D\u306E\u65E8\u3092\u793A\u3059\u3002\u65E7\u8A18\u9332\u306E\u4E0D\u660E\u306A\u65E5\u6642\u306F\u88DC\u308F\u306A\u3044\u3002\u300C\u65B0\u3057\u3044\u8A18\u9332\u300D\u306F\u771F\u507D\u306E\u4FDD\u8A3C\u3067\u306F\u306A\u3044\u3002\n- Done \u3067\u53E4\u3044\u72B6\u6CC1\u8AAC\u660E\u304C\u6B8B\u308B\u53EF\u80FD\u6027\u304C\u3042\u308B\u5834\u5408\u3001CLI\u30FB\u8A73\u7D30\u30D1\u30CD\u30EB\u30FBresume \u306B\u898B\u76F4\u3057\u6848\u5185\u304C\u51FA\u308B\u3002\u672C\u6587\u306F\u81EA\u52D5\u3067\u6D88\u3055\u306A\u3044\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089 set B12 --note "\u73FE\u5728\u306E\u8AAC\u660E" (none \u3067\u6D88\u53BB) \u307E\u305F\u306F\u753B\u9762\u306E Notes \u3067\u76F4\u3059\u3002\n- MCP \u306F boxglow_scope / boxglow_focus / boxglow_policy\u3001boxglow_start \u306E reason\u3001boxglow_resume \u306E includeCompleted \u3092\u4F7F\u3046\u3002\u5909\u66F4\u306B\u306F contextToken \u3092\u4ED8\u3051\u308B\u3002\n- \u30C7\u30FC\u30BF\u5F62\u5F0F\u306F v5 \u306E\u307E\u307E\u3067\u5168\u9805\u76EE\u306F\u4EFB\u610F\u3002\u65E7\u7248 (0.4.2\u4EE5\u524D) \u306F\u65B0\u3057\u3044\u898F\u5247\u3092\u5B9F\u884C\u305B\u305A\u3001\u4FDD\u5B58\u6642\u306B\u8A08\u753B\u306E workflowPolicy / focusBlockId \u3092\u843D\u3068\u3059\u3002\u3053\u308C\u3089\u3092\u4F7F\u3046\u8A08\u753B\u306E\u66F8\u304D\u624B (CLI\u30FBserve\u30FB\u753B\u9762\u30FB\u62E1\u5F35\u5185\u306E\u30A2\u30D7\u30EA) \u306F\u5BFE\u5FDC\u3057\u305F\u30D3\u30EB\u30C9\u306B\u63C3\u3048\u308B\u3002\u7248\u756A\u53F7\u3060\u3051\u3067\u306A\u304F\u30D3\u30EB\u30C9\u65E5\u6642\u3082\u78BA\u8A8D\u3059\u308B\u3002\n';
|
|
3739
|
+
// cli/sync/command.ts
|
|
3740
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
3438
3741
|
|
|
3439
|
-
//
|
|
3440
|
-
var SKILL_en_default = '---\nname: boxglow\ndescription: Share the plan and progress with humans through Boxglow (boxglow.json). Record starting, splitting, finishing and pending decisions with npx boxglow. Use for "record progress in Boxglow", "check the status with boxglow", or "/boxglow".\n---\n\n# Share the plan and progress with Boxglow\n\n`boxglow.json` at the repository root is the source of truth for the plan. Humans watch it on the Boxglow screen (https://boxglow.pages.dev/, "Open boxglow.json").\nDo not edit it directly; update it with `npx boxglow`.\n\n## Making the first plan (for a new project)\n\n1. Read the direction (README, the request, requirement notes), run `npx boxglow init --name "<name>"`, then place the top-level items (3-7 of them) with `add`.\n For each item, always write its "inputs (what it uses)" and "output (a concrete deliverable: file, URL, PR)", and connect them all the way to the final deliverable (`connect`)\n2. Once created, get a human\'s confirmation with `npx boxglow ask <first top-level item> "Is this breakdown into top-level items OK?" --options "OK|Needs changes"`.\n The human can answer on the screen, with `npx boxglow answer <block> "<answer>"`, or in chat. If the answer comes back in chat, the AI records it with `answer --by human`.\n If the human does not use the screen (when you are told to proceed with the AI alone), the AI records its own decision with `answer --by claude-code "<decision and reason>"` and moves on. The point is to keep a record of decisions; the screen is not required\n3. `split` only the item you are about to start (leave later stages coarse, and refine them as you go)\n\n## Steps\n\n1. Read `npx boxglow resume` (or `npx boxglow status --brief`). Take in the handoff notes, the answers not yet read by the AI, the pending decisions, the boxes in progress and the next candidates. Use `npx boxglow status` when you need the whole tree\n2. Once you have chosen a box, check its inputs and outputs with `npx boxglow show <block>`, then run `npx boxglow start <block> --note "<what you will do>"`.\n On a plan with the context guard on, first read `npx boxglow context <block>` and pass the `contextToken` (context token) from its output as `--context-token <context token>`\n3. Split a large box with `npx boxglow split <block> --spec \'<JSON>\'` (see `npx boxglow help` for the format). Always decide the output of each box\n4. When the deliverable exists, run `npx boxglow done <block> --artifact "<name>=<URL or path>"` (with the guard on, add `--context-token <context token>`; if your previous command printed "New context token: <token>", use that one)\n5. When a human decision is needed, run `npx boxglow ask <block> "<question>" --options "A|B"` and move on to another box\n6. When you are stuck, run `npx boxglow blocked <block> --note "<what is in the way>"`\n7. Before an interruption, context compaction or a handoff, save a handoff note with `npx boxglow checkpoint <block> --note "Findings; next steps; unresolved questions"`\n8. At the end, summarize `npx boxglow status --brief` and report it\n\n## Rules\n\n- Write each `ask` question so that it can be decided on its own. Do not point outside the question, as in "Is this OK?" or "the plan above"; put the premises, comparison and impact in `--context` (the human reads only the question in a list and answers it)\n- When choosing one of several candidates (an approach, a library, a design choice), even if the AI decides by itself, record it as `ask <block> "<question>" --options "A|B|C"` \u2192 `answer --by claude-code "<the choice>. Reason: ..."` so that the candidates not chosen stay on the box (you can come back to them when the direction changes)\n- To change direction, use `reopen <block> --note "<reason>"`: the previous answer stays in the history and you choose again (candidates are not deleted)\n\n- Keep at most 1-2 boxes in progress at a time\n- Make deliverables (artifacts) something a human can open later (a commit, a PR, a file path)\n- Name outputs after concrete deliverables (file, PR, URL, test results). Avoid abstract names (feature set, findings)\n- The receiving side of a connection can be just a title (`connect "A.Design doc" "B"` creates an input "Design doc" on B). An input\'s name is determined by the source output\'s name and is not changed on the input side\n- Always connect boxes as "input \u2192 output". Add missing inputs / outputs with `port`, remove a wire with `disconnect`, and remove a box itself with `remove`. Export a document with `export --out docs/ROADMAP.md`\n- When you add a box, give it a kind of work with `--category` (design / build / verify / evaluate / study / research / ui / improve / fix / docs / ops / other). On the screen it becomes a colored band and a tag\n- If boxglow.json does not exist yet, create it with `npx boxglow init --name "<project name>"` and place the final deliverable and the top-level boxes with `add`\n\n## Context token and handoff\n\nThe context guard keeps an AI from continuing work on outdated instructions. `setup-agent` turns it on (on other plans, run `npx boxglow guard on`).\nWhile it is on, an AI\'s `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` need `--context-token <context token>`. Commands run by a person (`--actor human`) do not.\n\n```\nnpx boxglow context B12 # read the decisions (including acknowledged answers), input/output contracts and handoff notes. contextToken in the output is the context token\nnpx boxglow start B12 --note "<what you will do>" --context-token <context token>\nnpx boxglow ask B12 "<question>" --options "A|B" --context-token <context token>\n# -> last line of the output: New context token: <token>\nnpx boxglow done B12 --artifact "<name>=<path>" --context-token <new context token>\n```\n\n- **The context token changes (you must reread)** when a person answers a decision or revises an answer, a person changes the instructions (title, description, input/output contracts), or another AI or a person updates the handoff note. The same applies when this happens on a parent box or an input provider. The old context token is rejected: reread `context`, take the change into account in your work, then retry\n- **The context token does not change** on `start` / `blocked` / `review` / `leave` / `ack`, on changes to progress, status, dates or category, when `check` verifies deliverables, or when boxes are moved on the screen. Keep using the same context token\n- **You can update it yourself**: when your own `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` etc. changes the context, the last line of its output is "New context token: <token>". Use it for the next command without rereading. `ask` / `decision` / `answer` / `reopen` run without a context token, but they print the new context token only when you pass the latest one\n- Do not turn the guard off to get past an old context token (an AI needs the latest context token to run `guard off`)\n- When resuming, read `npx boxglow resume`, and check a box that has a handoff note with `context` before continuing. Handoff notes stay in the plan, independent of the chat history and the activity-log limit\n- With MCP, use `boxglow_context` and the `contextToken` argument of each tool\n- If "Another Boxglow writer holds the file lock" persists after waiting and retrying, show the output of `npx boxglow unlock` (owner and verdict) to a person and ask them to remove it. Never add `--actor human` yourself or delete the lock folder directly\n\n\n### Readiness, current scope, and fresh resume information\n\n- Read current activity, pending decisions, and unread answers first. Candidates are grouped by current scope / other candidates, then ready / waiting for inputs, with missing input names. A waiting candidate is not ready.\n- Four optional scope fields are available: goal, non-goals, acceptance, and consult (when to consult before expanding scope). Read the target and parent scopes at the start of context before starting and finishing. Textual acceptance criteria are not automatically evaluated.\n- Use scope B12 --goal "Goal" --non-goals "Excluded" --acceptance "Checks" --consult "When to ask". scope B12 only reads; none or an empty string clears a field. In the inspector, use \u22EF \u2192 Edit work scope. Only populated fields appear in the collapsed scope section.\n- focus B12 prioritizes that box and its descendants; focus reads the selection, and focus none clears it. Other candidates remain in a separate group. Being listed does not authorize expanding scope or publishing.\n- Missing required inputs warn on start by default but still allow the start. Supply --reason "Why starting early is appropriate" to proceed without a warning and save the reason in both activity and the log. Artifact-free done also remains warning-only by default. Existing output artifacts or --artifact count; reference material on the box does not.\n- Only enable strict checks when selected for the plan: policy --start reject --done reject. Each can return to warn. A recorded start reason also overrides reject. The artifact requirement also applies to set --status white. Human operations (UI / --actor human) are never rejected. Do not impersonate a human or weaken policy to bypass rejection.\n- With guard enabled, scope writes, focus selection/clearing, and policy writes require --context-token. These changes print a new token: use it next. Read-only forms need no token.\n- resume hides completed handoff bodies and shows their count. Request resume --include-completed only when history is needed; context / show can still read individual notes.\n- descriptionUpdatedAt dates description changes; statusChangedAt dates status changes; handoffs use their existing at field. Notes predating a status change are marked. Unknown legacy dates stay unknown, and recency does not establish truth.\n- After Done, CLI, inspector, and resume flag descriptions that may still describe an earlier state. Text is never rewritten automatically. Review it, then update with set B12 --note "Current description" (none clears it) or Notes in the UI.\n- MCP: boxglow_scope / boxglow_focus / boxglow_policy, reason on boxglow_start, and includeCompleted on boxglow_resume. Pass contextToken for writes.\n- All data fields are optional; schema v5 is unchanged. Old versions (0.4.2 and earlier) do not enforce these checks and drop workflowPolicy / focusBlockId when saving. Align all writers (CLI, serve, web app, bundled extension app) to a supporting build before using these settings. Check build time as well as version.\n';
|
|
3742
|
+
// cli/sync/client.ts
|
|
3743
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3744
|
+
import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
3745
|
+
import { basename as basename3 } from "node:path";
|
|
3441
3746
|
|
|
3442
|
-
// src/model/
|
|
3443
|
-
var
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
const parts2 = [`- **${port2.name}**`];
|
|
3450
|
-
const desc = effectiveDescription(p, port2.id);
|
|
3451
|
-
if (desc) parts2.push(`: ${desc}`);
|
|
3452
|
-
if (port2.direction === "in" && !port2.required) parts2.push(" " + t("(\u4EFB\u610F)"));
|
|
3453
|
-
if (port2.artifacts.length > 0) {
|
|
3454
|
-
parts2.push(" / " + port2.artifacts.map((a) => a.url ? `[${a.title}](${a.url})` : a.title).join(", "));
|
|
3747
|
+
// src/model/merge.ts
|
|
3748
|
+
var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3749
|
+
function pick(path, base, ours, theirs, r) {
|
|
3750
|
+
if (same(ours, theirs)) return ours;
|
|
3751
|
+
if (same(ours, base)) {
|
|
3752
|
+
r.merged++;
|
|
3753
|
+
return theirs;
|
|
3455
3754
|
}
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3755
|
+
if (same(theirs, base)) return ours;
|
|
3756
|
+
const id = JSON.stringify(path);
|
|
3757
|
+
const adopted = r.choices[id] ?? (ours === void 0 && theirs !== void 0 ? "theirs" : "ours");
|
|
3758
|
+
r.conflicts.push({ id, path: path.join("."), segments: path, ours, theirs, adopted });
|
|
3759
|
+
return adopted === "theirs" ? theirs : ours;
|
|
3459
3760
|
}
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
const
|
|
3464
|
-
const
|
|
3465
|
-
|
|
3761
|
+
var TIMESTAMP_KEYS = /* @__PURE__ */ new Set(["descriptionUpdatedAt", "statusChangedAt", "lastSeen"]);
|
|
3762
|
+
function mergeObject(path, base, ours, theirs, r) {
|
|
3763
|
+
const out2 = {};
|
|
3764
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base ?? {})]);
|
|
3765
|
+
for (const k of keys) {
|
|
3766
|
+
const b = base?.[k];
|
|
3767
|
+
const o = ours[k];
|
|
3768
|
+
const t2 = theirs[k];
|
|
3769
|
+
if (TIMESTAMP_KEYS.has(k) && typeof o === "string" && typeof t2 === "string" && !same(o, t2) && !same(o, b) && !same(t2, b)) {
|
|
3770
|
+
out2[k] = Date.parse(t2) > Date.parse(o) ? t2 : o;
|
|
3771
|
+
continue;
|
|
3772
|
+
}
|
|
3773
|
+
const value = pick([...path, k], b, o, t2, r);
|
|
3774
|
+
if (value !== void 0) out2[k] = value;
|
|
3775
|
+
}
|
|
3776
|
+
return out2;
|
|
3466
3777
|
}
|
|
3467
|
-
function
|
|
3468
|
-
const
|
|
3469
|
-
|
|
3470
|
-
const
|
|
3471
|
-
|
|
3472
|
-
|
|
3778
|
+
function mergeMap(path, base, ours, theirs, r) {
|
|
3779
|
+
const out2 = {};
|
|
3780
|
+
const ids = /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base ?? {})]);
|
|
3781
|
+
for (const id of ids) {
|
|
3782
|
+
const b = base?.[id];
|
|
3783
|
+
const o = ours[id];
|
|
3784
|
+
const t2 = theirs[id];
|
|
3785
|
+
if (o && t2 && path[0] !== "handoffs") {
|
|
3786
|
+
out2[id] = mergeObject([...path, id], b, o, t2, r);
|
|
3787
|
+
continue;
|
|
3788
|
+
}
|
|
3789
|
+
const value = pick([...path, id], b, o, t2, r);
|
|
3790
|
+
if (value !== void 0) out2[id] = value;
|
|
3791
|
+
}
|
|
3792
|
+
return out2;
|
|
3473
3793
|
}
|
|
3474
|
-
function
|
|
3475
|
-
const
|
|
3476
|
-
|
|
3477
|
-
const
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3794
|
+
function mergeArrayById(path, base, ours, theirs, r) {
|
|
3795
|
+
const toMap = (xs) => Object.fromEntries((xs ?? []).map((x) => [x.id, x]));
|
|
3796
|
+
const merged = mergeMap(path, base ? toMap(base) : void 0, toMap(ours), toMap(theirs), r);
|
|
3797
|
+
const order = [...ours.map((x) => x.id), ...theirs.map((x) => x.id).filter((id) => !ours.some((x) => x.id === id))];
|
|
3798
|
+
return order.filter((id) => merged[id]).map((id) => merged[id]);
|
|
3799
|
+
}
|
|
3800
|
+
function restoreDeleted(out2, deleter, keeper, explicitlyDeleted) {
|
|
3801
|
+
const blocks = out2.blocks;
|
|
3802
|
+
const ports = out2.ports;
|
|
3803
|
+
const edges = out2.edges;
|
|
3804
|
+
const onlyInKeeper = (id) => !deleter.blocks[id] && !!keeper.blocks[id] && !explicitlyDeleted("blocks", id);
|
|
3805
|
+
const seeds = /* @__PURE__ */ new Set();
|
|
3806
|
+
for (const id of Object.keys(blocks)) if (onlyInKeeper(id)) seeds.add(id);
|
|
3807
|
+
for (const port2 of Object.values(ports)) {
|
|
3808
|
+
const id = port2.blockId;
|
|
3809
|
+
if (!blocks[id] && onlyInKeeper(id) && !deleter.ports[port2.id]) seeds.add(id);
|
|
3489
3810
|
}
|
|
3490
|
-
if (
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3811
|
+
if (seeds.size === 0) return;
|
|
3812
|
+
const tops = /* @__PURE__ */ new Set();
|
|
3813
|
+
for (const seed of seeds) {
|
|
3814
|
+
let top = seed;
|
|
3815
|
+
for (let parent = keeper.blocks[top]?.parentId; parent && onlyInKeeper(parent); parent = keeper.blocks[top]?.parentId) top = parent;
|
|
3816
|
+
tops.add(top);
|
|
3496
3817
|
}
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
const
|
|
3501
|
-
|
|
3502
|
-
|
|
3818
|
+
const restored = new Set(tops);
|
|
3819
|
+
for (let grew = true; grew; ) {
|
|
3820
|
+
grew = false;
|
|
3821
|
+
for (const b of Object.values(keeper.blocks)) {
|
|
3822
|
+
if (b.parentId && restored.has(b.parentId) && !restored.has(b.id) && !explicitlyDeleted("blocks", b.id)) {
|
|
3823
|
+
restored.add(b.id);
|
|
3824
|
+
grew = true;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3503
3827
|
}
|
|
3504
|
-
const
|
|
3505
|
-
const
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
lines.push(`**${t("\u51FA\u529B")}** (${t("{n} \u4EF6", { n: outs.length })})`);
|
|
3511
|
-
for (const q of outs) lines.push(portLine(p, q));
|
|
3512
|
-
if (b.artifacts.length > 0) {
|
|
3513
|
-
lines.push("");
|
|
3514
|
-
lines.push(`**${t("\u95A2\u9023\u8CC7\u6599")}**`);
|
|
3515
|
-
for (const a of b.artifacts) lines.push(`- ${a.url ? `[${a.title}](${a.url})` : a.title}${a.note ? `: ${a.note}` : ""}`);
|
|
3828
|
+
for (const id of restored) if (!blocks[id]) blocks[id] = keeper.blocks[id];
|
|
3829
|
+
for (const port2 of Object.values(keeper.ports)) if (restored.has(port2.blockId) && !ports[port2.id] && !explicitlyDeleted("ports", port2.id)) ports[port2.id] = port2;
|
|
3830
|
+
if (keeper.handoffs) {
|
|
3831
|
+
const handoffs = out2.handoffs ?? {};
|
|
3832
|
+
for (const id of restored) if (keeper.handoffs[id] && !handoffs[id] && !explicitlyDeleted("handoffs", id)) handoffs[id] = keeper.handoffs[id];
|
|
3833
|
+
out2.handoffs = handoffs;
|
|
3516
3834
|
}
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3835
|
+
for (const e of Object.values(keeper.edges)) {
|
|
3836
|
+
if (edges[e.id] || !ports[e.from.portId] || !ports[e.to.portId] || explicitlyDeleted("edges", e.id)) continue;
|
|
3837
|
+
const from = keeper.ports[e.from.portId]?.blockId;
|
|
3838
|
+
const to = keeper.ports[e.to.portId]?.blockId;
|
|
3839
|
+
if (from && restored.has(from) || to && restored.has(to)) edges[e.id] = e;
|
|
3520
3840
|
}
|
|
3521
|
-
return lines.join("\n");
|
|
3522
3841
|
}
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3842
|
+
var MERGED_ELSEWHERE = /* @__PURE__ */ new Set([
|
|
3843
|
+
"name",
|
|
3844
|
+
"description",
|
|
3845
|
+
"visibility",
|
|
3846
|
+
"terminals",
|
|
3847
|
+
"contextGuard",
|
|
3848
|
+
"workflowPolicy",
|
|
3849
|
+
"focusBlockId",
|
|
3850
|
+
"lang",
|
|
3851
|
+
"blocks",
|
|
3852
|
+
"ports",
|
|
3853
|
+
"edges",
|
|
3854
|
+
"agents",
|
|
3855
|
+
"handoffs",
|
|
3856
|
+
"members",
|
|
3857
|
+
"inputGroups",
|
|
3858
|
+
"log",
|
|
3859
|
+
"nextKey",
|
|
3860
|
+
"schemaVersion",
|
|
3861
|
+
"id",
|
|
3862
|
+
"createdAt",
|
|
3863
|
+
"updatedAt",
|
|
3864
|
+
"version"
|
|
3865
|
+
]);
|
|
3866
|
+
function mergeProjects(base, ours, theirs, choices = {}) {
|
|
3867
|
+
const r = { project: ours, merged: 0, conflicts: [], choices };
|
|
3868
|
+
const b = base ?? void 0;
|
|
3869
|
+
const o = ours;
|
|
3870
|
+
const th = theirs;
|
|
3871
|
+
const out2 = { ...o };
|
|
3872
|
+
for (const k of ["name", "description", "visibility", "terminals", "contextGuard", "workflowPolicy", "focusBlockId", "lang"]) {
|
|
3873
|
+
const value = pick([k], b?.[k], o[k], th[k], r);
|
|
3874
|
+
if (value !== void 0) out2[k] = value;
|
|
3875
|
+
else delete out2[k];
|
|
3876
|
+
}
|
|
3877
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(o), ...Object.keys(th), ...Object.keys(b ?? {})])) {
|
|
3878
|
+
if (MERGED_ELSEWHERE.has(k)) continue;
|
|
3879
|
+
const value = pick([k], b?.[k], o[k], th[k], r);
|
|
3880
|
+
if (value !== void 0) out2[k] = value;
|
|
3881
|
+
else delete out2[k];
|
|
3882
|
+
}
|
|
3883
|
+
for (const k of ["blocks", "ports", "edges", "agents", "handoffs"]) {
|
|
3884
|
+
out2[k] = mergeMap([k], b?.[k], o[k] ?? {}, th[k] ?? {}, r);
|
|
3885
|
+
}
|
|
3886
|
+
const explicitlyDeleted = (kind, id) => {
|
|
3887
|
+
const choice = choices[JSON.stringify([kind, id])];
|
|
3888
|
+
if (!choice) return false;
|
|
3889
|
+
return (choice === "ours" ? o : th)[kind]?.[id] === void 0;
|
|
3890
|
+
};
|
|
3891
|
+
restoreDeleted(out2, ours, theirs, explicitlyDeleted);
|
|
3892
|
+
restoreDeleted(out2, theirs, ours, explicitlyDeleted);
|
|
3893
|
+
const mergedBlocks = out2.blocks;
|
|
3894
|
+
for (const [id, box] of Object.entries(mergedBlocks)) {
|
|
3895
|
+
for (const [field, date] of [["description", "descriptionUpdatedAt"], ["status", "statusChangedAt"]]) {
|
|
3896
|
+
const left = ours.blocks[id], right = theirs.blocks[id];
|
|
3897
|
+
const matching = [left, right].filter((b2) => b2 && b2[field] === box[field]);
|
|
3898
|
+
if (!matching.length) continue;
|
|
3899
|
+
const times = matching.map((b2) => b2[date]).filter((x) => typeof x === "string").sort((a, b2) => Date.parse(a) - Date.parse(b2));
|
|
3900
|
+
if (times.length) box[date] = times[times.length - 1];
|
|
3901
|
+
else delete box[date];
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
if (out2.focusBlockId && !mergedBlocks[out2.focusBlockId]) delete out2.focusBlockId;
|
|
3905
|
+
const mergedPorts = out2.ports;
|
|
3906
|
+
const mergedEdges = out2.edges;
|
|
3907
|
+
for (const [id, port2] of Object.entries(mergedPorts)) if (!mergedBlocks[port2.blockId]) delete mergedPorts[id];
|
|
3908
|
+
for (const [id, e] of Object.entries(mergedEdges)) {
|
|
3909
|
+
const edge2 = e;
|
|
3910
|
+
if (!mergedPorts[edge2.from?.portId] || !mergedPorts[edge2.to?.portId]) delete mergedEdges[id];
|
|
3911
|
+
}
|
|
3912
|
+
const mergedHandoffs = out2.handoffs;
|
|
3913
|
+
for (const id of Object.keys(mergedHandoffs)) {
|
|
3914
|
+
if (!mergedBlocks[id] && (ours.blocks[id] || theirs.blocks[id] || base?.blocks[id])) delete mergedHandoffs[id];
|
|
3915
|
+
}
|
|
3916
|
+
if (Object.keys(mergedHandoffs).length === 0 && !ours.handoffs && !theirs.handoffs) delete out2.handoffs;
|
|
3917
|
+
out2.members = mergeArrayById(["members"], base?.members, ours.members ?? [], theirs.members ?? [], r);
|
|
3918
|
+
out2.inputGroups = mergeArrayById(["inputGroups"], base?.inputGroups, ours.inputGroups ?? [], theirs.inputGroups ?? [], r);
|
|
3919
|
+
const log = /* @__PURE__ */ new Map();
|
|
3920
|
+
for (const e of [...ours.log ?? [], ...theirs.log ?? []]) log.set(e.id, e);
|
|
3921
|
+
out2.log = [...log.values()].sort((p, q) => p.at < q.at ? -1 : p.at > q.at ? 1 : 0);
|
|
3922
|
+
const blocks = out2.blocks;
|
|
3923
|
+
const used = /* @__PURE__ */ new Map();
|
|
3924
|
+
let nextKey2 = Math.max(ours.nextKey ?? 1, theirs.nextKey ?? 1);
|
|
3925
|
+
for (const id of Object.keys(ours.blocks ?? {})) {
|
|
3926
|
+
const key = blocks[id]?.key;
|
|
3927
|
+
if (key) used.set(key, id);
|
|
3928
|
+
}
|
|
3929
|
+
for (const id of Object.keys(blocks)) {
|
|
3930
|
+
const key = blocks[id].key;
|
|
3931
|
+
if (!key) continue;
|
|
3932
|
+
const owner = used.get(key);
|
|
3933
|
+
if (owner && owner !== id) {
|
|
3934
|
+
blocks[id] = { ...blocks[id], key: `B${nextKey2}` };
|
|
3935
|
+
r.conflicts.push({ id: JSON.stringify(["blocks", id, "key"]), segments: ["blocks", id, "key"], automatic: true, path: `blocks.${id}.key`, ours: key, theirs: t("{key} \u306B\u632F\u308A\u76F4\u3057", { key: `B${nextKey2}` }) });
|
|
3936
|
+
nextKey2++;
|
|
3937
|
+
} else used.set(key, id);
|
|
3938
|
+
}
|
|
3939
|
+
out2.nextKey = nextKey2;
|
|
3940
|
+
r.project = out2;
|
|
3941
|
+
return { project: r.project, merged: r.merged, conflicts: r.conflicts };
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
// src/sync/engine.ts
|
|
3945
|
+
var PENDING_MAX_AGE_MS = 21 * 24 * 60 * 60 * 1e3;
|
|
3946
|
+
var CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
3947
|
+
var PROTECTED_KEYS = /* @__PURE__ */ new Set(["workflowPolicy", "focusBlockId", "contextGuard"]);
|
|
3948
|
+
function protectedDeletions(base, local) {
|
|
3949
|
+
return Object.keys(base).filter((key) => key !== "writer" && (PROTECTED_KEYS.has(key) || !KNOWN_PROJECT_KEYS.has(key))).filter((key) => base[key] !== void 0 && local[key] === void 0).sort();
|
|
3950
|
+
}
|
|
3951
|
+
function read(text2) {
|
|
3952
|
+
try {
|
|
3953
|
+
validateProjectText(text2);
|
|
3954
|
+
const project = fromJSON(text2);
|
|
3955
|
+
return { project, raw: JSON.parse(text2) };
|
|
3956
|
+
} catch (e) {
|
|
3957
|
+
return { problem: e instanceof Error ? e.message : String(e) };
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
var halt = (h) => ({ kind: "halt", halt: h });
|
|
3961
|
+
function decide(input) {
|
|
3962
|
+
const { state, local, remote, baseText, now: now2 } = input;
|
|
3963
|
+
if (state.pending?.kind === "push") {
|
|
3964
|
+
if (state.epoch !== null && remote.epoch !== state.epoch) return halt({ reason: "history-changed", expected: state.epoch, actual: remote.epoch });
|
|
3965
|
+
if (remote.kind === "deleted") return halt({ reason: "remote-deleted" });
|
|
3966
|
+
const at = Date.parse(state.pending.at);
|
|
3967
|
+
const age = now2.getTime() - at;
|
|
3968
|
+
if (!Number.isFinite(at) || age < -CLOCK_SKEW_MS || age > PENDING_MAX_AGE_MS) return halt({ reason: "stale-operation", pending: state.pending });
|
|
3969
|
+
return { kind: "resend", pending: state.pending };
|
|
3970
|
+
}
|
|
3971
|
+
if (state.pending?.kind === "pull") {
|
|
3972
|
+
if (local && local.hash === state.pending.hash) return { kind: "finish-pull", pending: state.pending };
|
|
3973
|
+
return halt({ reason: "recover-pull", pending: state.pending, localHash: local?.hash ?? null });
|
|
3974
|
+
}
|
|
3975
|
+
if (state.epoch !== null && remote.epoch !== state.epoch) return halt({ reason: "history-changed", expected: state.epoch, actual: remote.epoch });
|
|
3976
|
+
if (!state.base) {
|
|
3977
|
+
if (remote.kind === "deleted") return halt({ reason: "remote-deleted" });
|
|
3978
|
+
if (remote.kind === "absent") {
|
|
3979
|
+
if (!local) return { kind: "noop" };
|
|
3980
|
+
const l2 = read(local.text);
|
|
3981
|
+
if ("problem" in l2) return halt({ reason: "invalid-local", problem: l2.problem });
|
|
3982
|
+
return { kind: "push", epoch: remote.epoch, content: local, expected: null };
|
|
3983
|
+
}
|
|
3984
|
+
const r = read(remote.content.text);
|
|
3985
|
+
if ("problem" in r) return halt({ reason: "invalid-remote", problem: r.problem });
|
|
3986
|
+
const remoteBase2 = { hash: remote.content.hash, revision: remote.revision };
|
|
3987
|
+
if (!local) {
|
|
3988
|
+
return { kind: "pull", epoch: remote.epoch, write: remote.content, keep: [remote.content], pending: { kind: "pull", hash: remote.content.hash, expectedLocal: null, remote: remoteBase2, at: now2.toISOString() } };
|
|
3989
|
+
}
|
|
3990
|
+
if (local.hash === remote.content.hash) return { kind: "set-base", epoch: remote.epoch, base: remoteBase2, keep: [remote.content] };
|
|
3991
|
+
const token = input.hashOf(JSON.stringify(["first-link", input.bindingId, local.hash, remote.epoch, remote.revision, remote.content.hash])).slice(0, 16);
|
|
3992
|
+
if (input.firstLink?.token === token) {
|
|
3993
|
+
if (input.firstLink.prefer === "remote") {
|
|
3994
|
+
return {
|
|
3995
|
+
kind: "pull",
|
|
3996
|
+
epoch: remote.epoch,
|
|
3997
|
+
write: remote.content,
|
|
3998
|
+
keep: [remote.content],
|
|
3999
|
+
backup: true,
|
|
4000
|
+
usedChoice: "firstLink",
|
|
4001
|
+
pending: { kind: "pull", hash: remote.content.hash, expectedLocal: local.hash, remote: remoteBase2, at: now2.toISOString() }
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
4004
|
+
const l2 = read(local.text);
|
|
4005
|
+
if ("problem" in l2) return halt({ reason: "invalid-local", problem: l2.problem });
|
|
4006
|
+
return { kind: "push", epoch: remote.epoch, content: local, expected: remote.revision, usedChoice: "firstLink" };
|
|
4007
|
+
}
|
|
4008
|
+
return halt({ reason: "first-link", localHash: local.hash, remoteRevision: remote.revision, token });
|
|
4009
|
+
}
|
|
4010
|
+
const base = state.base;
|
|
4011
|
+
if (remote.kind !== "present") return halt({ reason: "remote-deleted" });
|
|
4012
|
+
if (!local) return halt({ reason: "local-missing" });
|
|
4013
|
+
const localChanged = local.hash !== base.hash;
|
|
4014
|
+
const remoteChanged = remote.revision !== base.revision;
|
|
4015
|
+
if (!localChanged && !remoteChanged) return { kind: "noop" };
|
|
4016
|
+
const remoteBase = { hash: remote.content.hash, revision: remote.revision };
|
|
4017
|
+
const baseContent = baseText !== null && input.hashOf(baseText) === base.hash ? baseText : remote.content.hash === base.hash ? remote.content.text : null;
|
|
4018
|
+
if (remoteChanged) {
|
|
4019
|
+
const r = read(remote.content.text);
|
|
4020
|
+
if ("problem" in r) return halt({ reason: "invalid-remote", problem: r.problem });
|
|
4021
|
+
if (local.hash === remote.content.hash) return { kind: "set-base", epoch: remote.epoch, base: remoteBase, keep: [remote.content] };
|
|
4022
|
+
const pendingFor = (write2) => ({ kind: "pull", hash: write2.hash, expectedLocal: local.hash, remote: remoteBase, at: now2.toISOString() });
|
|
4023
|
+
if (!localChanged) return { kind: "pull", epoch: remote.epoch, write: remote.content, keep: [remote.content], pending: pendingFor(remote.content) };
|
|
4024
|
+
if (baseContent === null) return halt({ reason: "base-missing" });
|
|
4025
|
+
const b = read(baseContent);
|
|
4026
|
+
if ("problem" in b) return halt({ reason: "base-missing" });
|
|
4027
|
+
const l2 = read(local.text);
|
|
4028
|
+
if ("problem" in l2) return halt({ reason: "invalid-local", problem: l2.problem });
|
|
4029
|
+
let merged = mergeProjects(b.project, l2.project, r.project);
|
|
4030
|
+
let resolved = false;
|
|
4031
|
+
const conflicts = merged.conflicts.filter((c) => !c.automatic);
|
|
4032
|
+
if (conflicts.length > 0) {
|
|
4033
|
+
const token = input.hashOf(JSON.stringify(["conflicts", input.bindingId, state.epoch, base.revision, base.hash, local.hash, remote.revision, conflicts.map((c) => c.id)])).slice(0, 16);
|
|
4034
|
+
if (input.resolution?.token !== token) return halt({ reason: "conflicts", base, localHash: local.hash, remoteRevision: remote.revision, conflicts, token });
|
|
4035
|
+
const side = input.resolution.prefer === "local" ? "ours" : "theirs";
|
|
4036
|
+
merged = mergeProjects(b.project, l2.project, r.project, Object.fromEntries(conflicts.map((c) => [c.id, side])));
|
|
4037
|
+
resolved = true;
|
|
4038
|
+
}
|
|
4039
|
+
const problem = projectProblem(merged.project);
|
|
4040
|
+
if (problem) return halt({ reason: "invalid-merge", problem });
|
|
4041
|
+
const text2 = toJSON(merged.project) + "\n";
|
|
4042
|
+
const write = { hash: input.hashOf(text2), text: text2 };
|
|
4043
|
+
const used = resolved ? { usedChoice: "resolution" } : {};
|
|
4044
|
+
if (write.hash === local.hash) return { kind: "set-base", epoch: remote.epoch, base: remoteBase, keep: [remote.content], ...used };
|
|
4045
|
+
return { kind: "pull", epoch: remote.epoch, write, keep: [write, remote.content], pending: pendingFor(write), ...used };
|
|
4046
|
+
}
|
|
4047
|
+
const l = read(local.text);
|
|
4048
|
+
if ("problem" in l) return halt({ reason: "invalid-local", problem: l.problem });
|
|
4049
|
+
if (baseContent === null) return halt({ reason: "base-missing" });
|
|
4050
|
+
let baseRaw;
|
|
4051
|
+
try {
|
|
4052
|
+
baseRaw = JSON.parse(baseContent);
|
|
4053
|
+
} catch {
|
|
4054
|
+
return halt({ reason: "base-missing" });
|
|
4055
|
+
}
|
|
4056
|
+
const keys = protectedDeletions(baseRaw, l.raw);
|
|
4057
|
+
if (keys.length > 0) {
|
|
4058
|
+
const approval = input.hashOf(JSON.stringify([input.bindingId, state.epoch, base.revision, base.hash, local.hash, keys]));
|
|
4059
|
+
if (input.restoreDeletion === approval) {
|
|
4060
|
+
const restored = { ...l.raw };
|
|
4061
|
+
for (const key of keys) restored[key] = baseRaw[key];
|
|
4062
|
+
const text2 = JSON.stringify(restored, null, 2) + "\n";
|
|
4063
|
+
const check = read(text2);
|
|
4064
|
+
if ("problem" in check) return halt({ reason: "invalid-local", problem: check.problem });
|
|
4065
|
+
return { kind: "edit-local", write: { hash: input.hashOf(text2), text: text2 }, expectedLocal: local.hash, usedChoice: "restore" };
|
|
4066
|
+
}
|
|
4067
|
+
if (input.approvedDeletion !== approval) return halt({ reason: "protected-deletion", keys, approval });
|
|
4068
|
+
return { kind: "push", epoch: remote.epoch, content: local, expected: base.revision, usedChoice: "adopt" };
|
|
4069
|
+
}
|
|
4070
|
+
return { kind: "push", epoch: remote.epoch, content: local, expected: base.revision };
|
|
4071
|
+
}
|
|
4072
|
+
var SyncStateError = class extends Error {
|
|
4073
|
+
};
|
|
4074
|
+
var samePending = (a, b) => a !== null && JSON.stringify(a) === JSON.stringify(b);
|
|
4075
|
+
function recordPending(state, epoch, pending) {
|
|
4076
|
+
if (state.pending !== null) throw new SyncStateError("another operation is still pending");
|
|
4077
|
+
return { ...state, generation: state.generation + 1, epoch, pending };
|
|
4078
|
+
}
|
|
4079
|
+
function pushRefused(state, opId, status, at) {
|
|
4080
|
+
if (state.pending?.kind !== "push" || state.pending.opId !== opId) throw new SyncStateError("the response does not belong to the pending push");
|
|
4081
|
+
return { ...state, generation: state.generation + 1, pending: { ...state.pending, rejected: { status, at } } };
|
|
4082
|
+
}
|
|
4083
|
+
function pushAccepted(state, opId, revision) {
|
|
4084
|
+
if (state.pending?.kind !== "push" || state.pending.opId !== opId) throw new SyncStateError("the response does not belong to the pending push");
|
|
4085
|
+
return { ...state, generation: state.generation + 1, base: { hash: state.pending.hash, revision }, pending: null };
|
|
4086
|
+
}
|
|
4087
|
+
function pushRejected(state, opId) {
|
|
4088
|
+
if (state.pending?.kind !== "push" || state.pending.opId !== opId) throw new SyncStateError("the response does not belong to the pending push");
|
|
4089
|
+
return { ...state, generation: state.generation + 1, pending: null };
|
|
4090
|
+
}
|
|
4091
|
+
function pullWritten(state, pending) {
|
|
4092
|
+
if (!samePending(state.pending, pending)) throw new SyncStateError("the pull does not match the pending operation");
|
|
4093
|
+
return { ...state, generation: state.generation + 1, base: pending.remote, pending: null };
|
|
4094
|
+
}
|
|
4095
|
+
function pullNotWritten(state, pending) {
|
|
4096
|
+
if (!samePending(state.pending, pending)) throw new SyncStateError("the pull does not match the pending operation");
|
|
4097
|
+
return { ...state, generation: state.generation + 1, pending: null };
|
|
4098
|
+
}
|
|
4099
|
+
function baseSet(state, epoch, base) {
|
|
4100
|
+
if (state.pending !== null) throw new SyncStateError("an operation is still pending");
|
|
4101
|
+
return { ...state, generation: state.generation + 1, epoch, base };
|
|
4102
|
+
}
|
|
4103
|
+
function recoveryToken(state, localHash, remote, bindingId, hashOf2) {
|
|
4104
|
+
return hashOf2(JSON.stringify(["recover", bindingId, state.generation, state.pending, localHash, remote.epoch, remote.revision])).slice(0, 16);
|
|
4105
|
+
}
|
|
4106
|
+
var remoteMark = (remote) => ({ epoch: remote.epoch, revision: remote.kind === "present" ? remote.revision : null });
|
|
4107
|
+
function pullRecovered(state, choice, localHash, remote, bindingId, hashOf2) {
|
|
4108
|
+
if (state.pending?.kind !== "pull") throw new SyncStateError("no pending pull");
|
|
4109
|
+
if (state.epoch !== null && remote.epoch !== state.epoch) throw new SyncStateError("the server history changed");
|
|
4110
|
+
if (choice.token !== recoveryToken(state, localHash, remote, bindingId, hashOf2)) throw new SyncStateError("the recovery choice was made for a different state");
|
|
4111
|
+
return choice.applied ? pullWritten(state, state.pending) : pullNotWritten(state, state.pending);
|
|
4112
|
+
}
|
|
4113
|
+
function describeChanges(fromText, toText, limit = 12) {
|
|
4114
|
+
let from, to;
|
|
4115
|
+
try {
|
|
4116
|
+
from = JSON.parse(fromText);
|
|
4117
|
+
to = JSON.parse(toText);
|
|
4118
|
+
} catch {
|
|
4119
|
+
return [t("(\u4E2D\u8EAB\u3092\u8AAD\u3081\u306A\u3044\u305F\u3081\u3001\u9055\u3044\u3092\u8868\u793A\u3067\u304D\u307E\u305B\u3093)")];
|
|
4120
|
+
}
|
|
4121
|
+
const lines = [];
|
|
4122
|
+
const same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
4123
|
+
const blocksOf = (d) => d.blocks ?? {};
|
|
4124
|
+
const fromBlocks = blocksOf(from), toBlocks = blocksOf(to);
|
|
4125
|
+
const title = (b) => String(b?.title ?? "");
|
|
4126
|
+
for (const id of Object.keys(toBlocks)) {
|
|
4127
|
+
if (!fromBlocks[id]) {
|
|
4128
|
+
lines.push(t("\u30DC\u30C3\u30AF\u30B9\u300C{title}\u300D\u304C\u52A0\u308F\u308B", { title: title(toBlocks[id]) }));
|
|
4129
|
+
continue;
|
|
4130
|
+
}
|
|
4131
|
+
const fields = [.../* @__PURE__ */ new Set([...Object.keys(fromBlocks[id]), ...Object.keys(toBlocks[id])])].filter((k) => k !== "descriptionUpdatedAt" && k !== "statusChangedAt" && !same2(fromBlocks[id][k], toBlocks[id][k]));
|
|
4132
|
+
if (fields.length > 0) lines.push(t("\u30DC\u30C3\u30AF\u30B9\u300C{title}\u300D\u306E {fields} \u304C\u5909\u308F\u308B", { title: title(toBlocks[id]), fields: fields.join(", ") }));
|
|
4133
|
+
}
|
|
4134
|
+
for (const id of Object.keys(fromBlocks)) if (!toBlocks[id]) lines.push(t("\u30DC\u30C3\u30AF\u30B9\u300C{title}\u300D\u304C\u6D88\u3048\u308B", { title: title(fromBlocks[id]) }));
|
|
4135
|
+
for (const kind of ["ports", "edges"]) {
|
|
4136
|
+
const a = from[kind] ?? {}, b = to[kind] ?? {};
|
|
4137
|
+
const changed = [.../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])].filter((id) => !same2(a[id], b[id])).length;
|
|
4138
|
+
if (changed > 0) lines.push(kind === "ports" ? t("\u5165\u51FA\u529B\u304C {count} \u4EF6\u5909\u308F\u308B", { count: changed }) : t("\u7DDA\u304C {count} \u4EF6\u5909\u308F\u308B", { count: changed }));
|
|
4139
|
+
}
|
|
4140
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(from), ...Object.keys(to)])) {
|
|
4141
|
+
if (["blocks", "ports", "edges", "log", "agents", "nextKey", "writer"].includes(key)) continue;
|
|
4142
|
+
if (!same2(from[key], to[key])) lines.push(to[key] === void 0 ? t("\u8A2D\u5B9A {key} \u304C\u6D88\u3048\u308B", { key }) : from[key] === void 0 ? t("\u8A2D\u5B9A {key} \u304C\u52A0\u308F\u308B", { key }) : t("\u8A2D\u5B9A {key} \u304C\u5909\u308F\u308B", { key }));
|
|
4143
|
+
}
|
|
4144
|
+
if (lines.length > limit) return [...lines.slice(0, limit), t("\u307B\u304B {count} \u4EF6", { count: lines.length - limit })];
|
|
4145
|
+
return lines;
|
|
4146
|
+
}
|
|
4147
|
+
function previewRecovery(input, objects) {
|
|
4148
|
+
const pending = input.state.pending;
|
|
4149
|
+
if (pending?.kind !== "pull") throw new SyncStateError("no pending pull");
|
|
4150
|
+
const remoteText = input.remote.kind === "present" ? input.remote.content.text : "{}";
|
|
4151
|
+
const outcome = (applied) => {
|
|
4152
|
+
let state = applied ? pullWritten(input.state, pending) : pullNotWritten(input.state, pending);
|
|
4153
|
+
let local = input.local;
|
|
4154
|
+
const kept = /* @__PURE__ */ new Map();
|
|
4155
|
+
const lookup = (hash) => kept.get(hash) ?? objects(hash);
|
|
4156
|
+
const changes = () => describeChanges(input.local?.text ?? "{}", local?.text ?? "{}");
|
|
4157
|
+
for (let step = 0; step < 8; step++) {
|
|
4158
|
+
const decision2 = decide({ ...input, state, local, baseText: state.base ? lookup(state.base.hash) : null });
|
|
4159
|
+
if (decision2.kind === "halt") return { next: decision2.halt.reason, localChanges: changes(), remoteChanges: [] };
|
|
4160
|
+
if (decision2.kind === "noop") return { next: "none", localChanges: changes(), remoteChanges: [] };
|
|
4161
|
+
if (decision2.kind === "push") return { next: "push", localChanges: changes(), remoteChanges: describeChanges(remoteText, decision2.content.text) };
|
|
4162
|
+
if (decision2.kind === "set-base") {
|
|
4163
|
+
for (const c of decision2.keep) kept.set(c.hash, c.text);
|
|
4164
|
+
state = baseSet(state, decision2.epoch, decision2.base);
|
|
4165
|
+
continue;
|
|
4166
|
+
}
|
|
4167
|
+
if (decision2.kind === "pull") {
|
|
4168
|
+
for (const c of decision2.keep) kept.set(c.hash, c.text);
|
|
4169
|
+
local = decision2.write;
|
|
4170
|
+
state = pullWritten(recordPending(state, decision2.epoch, decision2.pending), decision2.pending);
|
|
4171
|
+
continue;
|
|
4172
|
+
}
|
|
4173
|
+
if (decision2.kind === "edit-local") {
|
|
4174
|
+
local = decision2.write;
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
break;
|
|
4178
|
+
}
|
|
4179
|
+
return { next: "none", localChanges: changes(), remoteChanges: [] };
|
|
4180
|
+
};
|
|
4181
|
+
return { applied: outcome(true), notApplied: outcome(false) };
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
// cli/sync/state-store.ts
|
|
4185
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
4186
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, realpathSync as realpathSync3, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4187
|
+
import { homedir } from "node:os";
|
|
4188
|
+
import { basename as basename2, dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
|
|
4189
|
+
var hashOf = (text2) => createHash3("sha256").update(text2).digest("hex");
|
|
4190
|
+
var SyncStateUnreadable = class extends Error {
|
|
4191
|
+
constructor(path, problem) {
|
|
4192
|
+
super(`sync state is unreadable (${problem}): ${path}`);
|
|
4193
|
+
this.path = path;
|
|
4194
|
+
this.problem = problem;
|
|
4195
|
+
}
|
|
4196
|
+
path;
|
|
4197
|
+
problem;
|
|
4198
|
+
};
|
|
4199
|
+
function stateProblem(value) {
|
|
4200
|
+
const text2 = (x) => typeof x === "string" && x.length > 0;
|
|
4201
|
+
const obj = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
|
|
4202
|
+
if (!obj(value)) return "not an object";
|
|
4203
|
+
if (value.version !== 1) return "unknown version";
|
|
4204
|
+
if (!Number.isSafeInteger(value.generation) || value.generation < 0) return "generation";
|
|
4205
|
+
if (value.epoch !== null && !text2(value.epoch)) return "epoch";
|
|
4206
|
+
const b = value.binding;
|
|
4207
|
+
if (!obj(b) || !text2(b.file) || !text2(b.server) || !text2(b.remoteId) || typeof b.planId !== "string") return "binding";
|
|
4208
|
+
if (b.account !== void 0 && !text2(b.account)) return "binding account";
|
|
4209
|
+
if (b.pendingPlanId !== void 0 && (typeof b.pendingPlanId !== "string" || !obj(value.pending) || value.pending.kind !== "pull")) return "pending plan id";
|
|
4210
|
+
const base = value.base;
|
|
4211
|
+
if (base !== null && (!obj(base) || !text2(base.hash) || !text2(base.revision))) return "base";
|
|
4212
|
+
const p = value.pending;
|
|
4213
|
+
if (p !== null) {
|
|
4214
|
+
if (!obj(p) || !text2(p.hash) || !text2(p.at)) return "pending";
|
|
4215
|
+
if (p.kind === "push") {
|
|
4216
|
+
if (!text2(p.opId) || p.expected !== null && !text2(p.expected)) return "pending push";
|
|
4217
|
+
if (p.rejected !== void 0 && (!obj(p.rejected) || !Number.isSafeInteger(p.rejected.status) || !text2(p.rejected.at))) return "pending push rejection";
|
|
4218
|
+
} else if (p.kind === "pull") {
|
|
4219
|
+
if (p.expectedLocal !== null && !text2(p.expectedLocal) || !obj(p.remote) || !text2(p.remote.hash) || !text2(p.remote.revision)) return "pending pull";
|
|
4220
|
+
} else return "pending kind";
|
|
4221
|
+
if (value.epoch === null) return "pending without epoch";
|
|
4222
|
+
}
|
|
4223
|
+
return null;
|
|
4224
|
+
}
|
|
4225
|
+
var StateConflict = class extends Error {
|
|
4226
|
+
constructor() {
|
|
4227
|
+
super("sync state changed by another process");
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
function configDir() {
|
|
4231
|
+
if (process.env.BOXGLOW_CONFIG_DIR) return resolve3(process.env.BOXGLOW_CONFIG_DIR);
|
|
4232
|
+
if (process.env.XDG_CONFIG_HOME) return join4(process.env.XDG_CONFIG_HOME, "boxglow");
|
|
4233
|
+
if (process.platform === "win32" && process.env.APPDATA) return join4(process.env.APPDATA, "boxglow");
|
|
4234
|
+
return join4(homedir(), ".config", "boxglow");
|
|
4235
|
+
}
|
|
4236
|
+
var normalizeServer = (server) => server.replace(/\/+$/, "");
|
|
4237
|
+
function realFile(file) {
|
|
4238
|
+
let at = resolve3(file);
|
|
4239
|
+
const rest = [];
|
|
4240
|
+
while (!existsSync4(at)) {
|
|
4241
|
+
const parent = dirname4(at);
|
|
4242
|
+
if (parent === at) return resolve3(file);
|
|
4243
|
+
rest.unshift(basename2(at));
|
|
4244
|
+
at = parent;
|
|
4245
|
+
}
|
|
4246
|
+
return join4(realpathSync3(at), ...rest);
|
|
4247
|
+
}
|
|
4248
|
+
function bindingDir(file, server) {
|
|
4249
|
+
return join4(configDir(), "sync", hashOf(`${normalizeServer(server)}
|
|
4250
|
+
${realFile(file)}`).slice(0, 32));
|
|
4251
|
+
}
|
|
4252
|
+
function bindingsOf(file) {
|
|
4253
|
+
const real = realFile(file);
|
|
4254
|
+
const root = join4(configDir(), "sync");
|
|
4255
|
+
const bindings = [], unreadable = [];
|
|
4256
|
+
let names = [];
|
|
4257
|
+
try {
|
|
4258
|
+
names = readdirSync2(root);
|
|
4259
|
+
} catch (e) {
|
|
4260
|
+
if (e.code !== "ENOENT") throw e;
|
|
4261
|
+
}
|
|
4262
|
+
for (const name of names) {
|
|
4263
|
+
const dir2 = join4(root, name);
|
|
4264
|
+
if (!existsSync4(join4(dir2, "state.json"))) continue;
|
|
4265
|
+
try {
|
|
4266
|
+
const state = new StateStore(dir2).read();
|
|
4267
|
+
if (state && state.binding.file === real) bindings.push(state.binding);
|
|
4268
|
+
} catch {
|
|
4269
|
+
unreadable.push(dir2);
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
return { bindings, unreadable };
|
|
4273
|
+
}
|
|
4274
|
+
function writeAtomic(path, text2) {
|
|
4275
|
+
const temp = `${path}.${randomUUID2()}.tmp`;
|
|
4276
|
+
writeFileSync3(temp, text2, { encoding: "utf8", mode: 384 });
|
|
4277
|
+
try {
|
|
4278
|
+
renameSync2(temp, path);
|
|
4279
|
+
} catch (e) {
|
|
4280
|
+
try {
|
|
4281
|
+
rmSync2(temp, { force: true });
|
|
4282
|
+
} catch {
|
|
4283
|
+
}
|
|
4284
|
+
throw e;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
function lockFileSync(file) {
|
|
4288
|
+
const root = join4(configDir(), "sync");
|
|
4289
|
+
mkdirSync3(root, { recursive: true, mode: 448 });
|
|
4290
|
+
try {
|
|
4291
|
+
return lockFile(join4(root, `file-${hashOf(file).slice(0, 32)}`), { waitMs: 0 });
|
|
4292
|
+
} catch (e) {
|
|
4293
|
+
if (e instanceof FileBusy) return null;
|
|
4294
|
+
throw e;
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
var StateStore = class {
|
|
4298
|
+
constructor(dir2) {
|
|
4299
|
+
this.dir = dir2;
|
|
4300
|
+
}
|
|
4301
|
+
dir;
|
|
4302
|
+
get statePath() {
|
|
4303
|
+
return join4(this.dir, "state.json");
|
|
4304
|
+
}
|
|
4305
|
+
/**
|
|
4306
|
+
* 状態用のロックを取る (同じ結び付けを扱う同期の処理を 1 つに絞る)
|
|
4307
|
+
* Output: ロックを外す関数。他の同期の処理が動いていれば null (待たない)
|
|
4308
|
+
*/
|
|
4309
|
+
lock() {
|
|
4310
|
+
mkdirSync3(join4(this.dir, "objects"), { recursive: true, mode: 448 });
|
|
4311
|
+
try {
|
|
4312
|
+
return lockFile(this.statePath, { waitMs: 0 });
|
|
4313
|
+
} catch (e) {
|
|
4314
|
+
if (e instanceof FileBusy) return null;
|
|
4315
|
+
throw e;
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
/** 状態を読む。まだ無ければ null。壊れている・形が合わなければ例外 (推測で作り直さない。読めない操作の記録を「無いもの」として扱わない) */
|
|
4319
|
+
read() {
|
|
4320
|
+
if (!existsSync4(this.statePath)) return null;
|
|
4321
|
+
let value;
|
|
4322
|
+
try {
|
|
4323
|
+
value = JSON.parse(readFileSync4(this.statePath, "utf8"));
|
|
4324
|
+
} catch {
|
|
4325
|
+
throw new SyncStateUnreadable(this.statePath, "not JSON");
|
|
4326
|
+
}
|
|
4327
|
+
const problem = stateProblem(value);
|
|
4328
|
+
if (problem) throw new SyncStateUnreadable(this.statePath, problem);
|
|
4329
|
+
return value;
|
|
4330
|
+
}
|
|
4331
|
+
/**
|
|
4332
|
+
* 状態を書く (読んだ世代のままなら置き換える)
|
|
4333
|
+
* Input : next = 新しい状態, expectedGeneration = 読んだときの世代 (初めて書くときは null)
|
|
4334
|
+
* Output: なし。世代が違っていたら StateConflict
|
|
4335
|
+
*/
|
|
4336
|
+
write(next, expectedGeneration) {
|
|
4337
|
+
const current2 = this.read();
|
|
4338
|
+
if ((current2?.generation ?? null) !== expectedGeneration) throw new StateConflict();
|
|
4339
|
+
writeAtomic(this.statePath, JSON.stringify(next, null, 2) + "\n");
|
|
4340
|
+
}
|
|
4341
|
+
/**
|
|
4342
|
+
* 中身の写しを置く。state.json がそのハッシュを指す前に、必ず先に置く
|
|
4343
|
+
* Input : text = 中身
|
|
4344
|
+
* Output: 中身のハッシュ。返したハッシュの写しは、必ず読み戻せる
|
|
4345
|
+
* (同じハッシュの写しが既にあっても、中身が壊れていたら置き直す。置き直せなければ例外にして、状態を進ませない)
|
|
4346
|
+
*/
|
|
4347
|
+
putObject(text2) {
|
|
4348
|
+
const hash = hashOf(text2);
|
|
4349
|
+
if (this.getObject(hash) === null) {
|
|
4350
|
+
writeAtomic(join4(this.dir, "objects", hash), text2);
|
|
4351
|
+
if (this.getObject(hash) === null) throw new Error("could not store a content copy in the sync state folder: " + hash);
|
|
4352
|
+
}
|
|
4353
|
+
return hash;
|
|
4354
|
+
}
|
|
4355
|
+
/** 中身の写しを読む。無い・中身がハッシュと合わない (壊れている) なら null */
|
|
4356
|
+
getObject(hash) {
|
|
4357
|
+
try {
|
|
4358
|
+
const text2 = readFileSync4(join4(this.dir, "objects", hash), "utf8");
|
|
4359
|
+
return hashOf(text2) === hash ? text2 : null;
|
|
4360
|
+
} catch {
|
|
4361
|
+
return null;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
};
|
|
4365
|
+
|
|
4366
|
+
// cli/sync/client.ts
|
|
4367
|
+
var SyncNetworkError = class extends Error {
|
|
4368
|
+
};
|
|
4369
|
+
var SyncAuthError = class extends SyncNetworkError {
|
|
4370
|
+
constructor(status) {
|
|
4371
|
+
super(`not authorized (${status})`);
|
|
4372
|
+
this.status = status;
|
|
4373
|
+
}
|
|
4374
|
+
status;
|
|
4375
|
+
};
|
|
4376
|
+
var SyncRejectedError = class extends Error {
|
|
4377
|
+
constructor(status, detail) {
|
|
4378
|
+
super(`the server rejected the request (${status}): ${detail}`);
|
|
4379
|
+
this.status = status;
|
|
4380
|
+
this.detail = detail;
|
|
4381
|
+
}
|
|
4382
|
+
status;
|
|
4383
|
+
detail;
|
|
4384
|
+
};
|
|
4385
|
+
var TERMINAL_STATUS = /* @__PURE__ */ new Set([400, 413, 422, 426, 428, 507]);
|
|
4386
|
+
function rejectUnauthorized(res) {
|
|
4387
|
+
if (res.status === 401 || res.status === 403) throw new SyncAuthError(res.status);
|
|
4388
|
+
}
|
|
4389
|
+
function recoveryToken2(state, localHash, remote, bindingId) {
|
|
4390
|
+
return recoveryToken(state, localHash, remote, bindingId, hashOf);
|
|
4391
|
+
}
|
|
4392
|
+
var bindingIdOf = (dir2, remoteId, account) => `${basename3(dir2)}:${remoteId}:${account}`;
|
|
4393
|
+
function accountOf(res) {
|
|
4394
|
+
const account = res.headers.get("x-boxglow-account");
|
|
4395
|
+
if (!account) throw new SyncNetworkError(`unexpected response ${res.status} (no account)`);
|
|
4396
|
+
return account;
|
|
4397
|
+
}
|
|
4398
|
+
async function fetchRemote(o) {
|
|
4399
|
+
let res;
|
|
4400
|
+
try {
|
|
4401
|
+
res = await o.fetch(`${o.server}/v1/projects/${encodeURIComponent(o.remoteId)}`, { headers: headers(o.token) });
|
|
4402
|
+
} catch (e) {
|
|
4403
|
+
throw new SyncNetworkError(String(e));
|
|
4404
|
+
}
|
|
4405
|
+
rejectUnauthorized(res);
|
|
4406
|
+
const epoch = res.headers.get("x-boxglow-epoch");
|
|
4407
|
+
if (!epoch) throw new SyncNetworkError(`unexpected response ${res.status} (no epoch)`);
|
|
4408
|
+
const account = accountOf(res);
|
|
4409
|
+
if (res.status === 404) return { remote: { kind: "absent", epoch }, account };
|
|
4410
|
+
if (res.status === 410) return { remote: { kind: "deleted", epoch }, account };
|
|
4411
|
+
if (res.status !== 200) throw new SyncNetworkError(`unexpected response ${res.status}`);
|
|
4412
|
+
const revision = unquote(res.headers.get("etag"));
|
|
4413
|
+
if (!revision) throw new SyncNetworkError("no ETag");
|
|
4414
|
+
let text2;
|
|
4415
|
+
try {
|
|
4416
|
+
text2 = await res.text();
|
|
4417
|
+
} catch (e) {
|
|
4418
|
+
throw new SyncNetworkError(`could not read the response body: ${String(e)}`);
|
|
4419
|
+
}
|
|
4420
|
+
return { remote: { kind: "present", epoch, revision, content: { hash: hashOf(text2), text: text2 } }, account };
|
|
4421
|
+
}
|
|
4422
|
+
async function settleOperation(o, opId) {
|
|
4423
|
+
let res;
|
|
4424
|
+
try {
|
|
4425
|
+
res = await o.fetch(`${o.server}/v1/projects/${encodeURIComponent(o.remoteId)}/ops/${encodeURIComponent(opId)}/settle`, { method: "POST", headers: { ...headers(o.token), "x-boxglow-epoch": o.epoch } });
|
|
4426
|
+
} catch (e) {
|
|
4427
|
+
throw new SyncNetworkError(String(e));
|
|
4428
|
+
}
|
|
4429
|
+
rejectUnauthorized(res);
|
|
4430
|
+
if (res.status === 409) return { history: res.headers.get("x-boxglow-epoch") ?? "" };
|
|
4431
|
+
if (res.status !== 200) throw new SyncNetworkError(`unexpected response ${res.status}`);
|
|
4432
|
+
const account = accountOf(res);
|
|
4433
|
+
let body;
|
|
4434
|
+
try {
|
|
4435
|
+
body = await res.json();
|
|
4436
|
+
} catch (e) {
|
|
4437
|
+
throw new SyncNetworkError(`could not read the response body: ${String(e)}`);
|
|
4438
|
+
}
|
|
4439
|
+
const answer = body;
|
|
4440
|
+
if (typeof answer?.revision === "string" && answer.revision !== "") return { account, revision: answer.revision };
|
|
4441
|
+
if (answer?.revision === null && answer.cancelled === true) return { account, revision: null };
|
|
4442
|
+
throw new SyncNetworkError("unexpected response body (operation)");
|
|
4443
|
+
}
|
|
4444
|
+
async function fetchHeads(o) {
|
|
4445
|
+
let res;
|
|
4446
|
+
try {
|
|
4447
|
+
res = await (o.fetch ?? fetch)(`${normalizeServer(o.server)}/v1/projects`, { headers: headers(o.token) });
|
|
4448
|
+
} catch (e) {
|
|
4449
|
+
throw new SyncNetworkError(String(e));
|
|
4450
|
+
}
|
|
4451
|
+
rejectUnauthorized(res);
|
|
4452
|
+
const epoch = res.headers.get("x-boxglow-epoch");
|
|
4453
|
+
if (res.status !== 200 || !epoch) throw new SyncNetworkError(`unexpected response ${res.status}`);
|
|
4454
|
+
const account = accountOf(res);
|
|
4455
|
+
let list2;
|
|
4456
|
+
try {
|
|
4457
|
+
list2 = await res.json();
|
|
4458
|
+
} catch (e) {
|
|
4459
|
+
throw new SyncNetworkError(`could not read the response body: ${String(e)}`);
|
|
4460
|
+
}
|
|
4461
|
+
if (!Array.isArray(list2)) throw new SyncNetworkError("unexpected response body (not a list)");
|
|
4462
|
+
const heads = /* @__PURE__ */ new Map();
|
|
4463
|
+
for (const item of list2) {
|
|
4464
|
+
const p = item;
|
|
4465
|
+
if (typeof p?.id !== "string" || p.revision !== null && typeof p.revision !== "string" || typeof p.deleted !== "boolean" || heads.has(p.id)) throw new SyncNetworkError("unexpected response body (bad list item)");
|
|
4466
|
+
heads.set(p.id, { revision: p.revision, deleted: p.deleted });
|
|
4467
|
+
}
|
|
4468
|
+
return { epoch, account, heads };
|
|
4469
|
+
}
|
|
4470
|
+
function headers(token) {
|
|
4471
|
+
return { ...token ? { authorization: `Bearer ${token}` } : {}, "x-boxglow-version": APP_VERSION, "x-boxglow-protocol": String(SAVE_PROTOCOL) };
|
|
4472
|
+
}
|
|
4473
|
+
var unquote = (etag) => etag ? etag.replace(/^W\//, "").replace(/^"|"$/g, "") : null;
|
|
4474
|
+
async function sendPush(o, pending, text2) {
|
|
4475
|
+
let res;
|
|
4476
|
+
try {
|
|
4477
|
+
res = await o.fetch(`${o.server}/v1/projects/${encodeURIComponent(o.remoteId)}`, {
|
|
4478
|
+
method: "PUT",
|
|
4479
|
+
headers: {
|
|
4480
|
+
...headers(o.token),
|
|
4481
|
+
"content-type": "application/json",
|
|
4482
|
+
"x-boxglow-op": pending.opId,
|
|
4483
|
+
...o.epoch ? { "x-boxglow-epoch": o.epoch } : {},
|
|
4484
|
+
...pending.expected === null ? { "if-none-match": "*" } : { "if-match": `"${pending.expected}"` }
|
|
4485
|
+
},
|
|
4486
|
+
body: text2
|
|
4487
|
+
});
|
|
4488
|
+
} catch (e) {
|
|
4489
|
+
throw new SyncNetworkError(String(e));
|
|
4490
|
+
}
|
|
4491
|
+
rejectUnauthorized(res);
|
|
4492
|
+
if (res.status === 200 || res.status === 201) {
|
|
4493
|
+
const revision = unquote(res.headers.get("etag"));
|
|
4494
|
+
if (!revision) throw new SyncNetworkError("no ETag");
|
|
4495
|
+
return { accepted: revision };
|
|
4496
|
+
}
|
|
4497
|
+
if (res.status === 412) return { rejected: true };
|
|
4498
|
+
if (res.status === 409) return { history: res.headers.get("x-boxglow-epoch") ?? "" };
|
|
4499
|
+
if (res.status === 410) return { deleted: true };
|
|
4500
|
+
let detail = "";
|
|
4501
|
+
try {
|
|
4502
|
+
detail = (await res.text()).slice(0, 200);
|
|
4503
|
+
} catch {
|
|
4504
|
+
}
|
|
4505
|
+
if (TERMINAL_STATUS.has(res.status)) throw new SyncRejectedError(res.status, detail);
|
|
4506
|
+
throw new SyncNetworkError(`unexpected response ${res.status}: ${detail}`);
|
|
4507
|
+
}
|
|
4508
|
+
async function syncOnce(options) {
|
|
4509
|
+
const now2 = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
4510
|
+
const doFetch = options.fetch ?? fetch;
|
|
4511
|
+
const server = normalizeServer(options.server);
|
|
4512
|
+
const file = realFile(options.file);
|
|
4513
|
+
let pulled = 0, pushed = 0, edited = 0;
|
|
4514
|
+
const target = { server, remoteId: options.remoteId ?? "", file };
|
|
4515
|
+
let lastLocal;
|
|
4516
|
+
const halted = (halt2, extra = {}) => ({ status: "halted", halt: halt2, pulled, pushed, edited, target, localHash: lastLocal, ...extra });
|
|
4517
|
+
const unlockFile = lockFileSync(file);
|
|
4518
|
+
if (!unlockFile) return { status: "busy", what: "sync" };
|
|
4519
|
+
let unlock = null;
|
|
4520
|
+
try {
|
|
4521
|
+
const known = bindingsOf(file);
|
|
4522
|
+
const elsewhere = known.bindings.find((b) => b.server !== server);
|
|
4523
|
+
if (elsewhere) return halted({ reason: "bound-elsewhere", server: elsewhere.server });
|
|
4524
|
+
const store = new StateStore(bindingDir(file, server));
|
|
4525
|
+
unlock = store.lock();
|
|
4526
|
+
if (!unlock) return { status: "busy", what: "sync" };
|
|
4527
|
+
let stored = store.read();
|
|
4528
|
+
if (!stored && known.unreadable.length > 0) return halted({ reason: "unreadable-bindings", dirs: known.unreadable });
|
|
4529
|
+
if (stored && options.remoteId !== void 0 && options.remoteId !== stored.binding.remoteId) {
|
|
4530
|
+
return halted({ reason: "binding-target", bound: stored.binding.remoteId, requested: options.remoteId });
|
|
4531
|
+
}
|
|
4532
|
+
const save2 = (next, binding) => {
|
|
4533
|
+
const value = { ...next, version: 1, binding };
|
|
4534
|
+
store.write(value, stored?.generation ?? null);
|
|
4535
|
+
stored = value;
|
|
4536
|
+
};
|
|
4537
|
+
for (let round = 0; round < 12; round++) {
|
|
4538
|
+
const localText = existsSync5(file) ? readFileSync5(file, "utf8") : null;
|
|
4539
|
+
const local = localText === null ? null : { hash: hashOf(localText), text: localText };
|
|
4540
|
+
const planId = localText === null ? null : planIdOf(localText);
|
|
4541
|
+
lastLocal = local?.hash ?? null;
|
|
4542
|
+
await options.onStep?.("local:read");
|
|
4543
|
+
let binding = stored?.binding ?? { file, server, remoteId: options.remoteId ?? randomUUID3(), planId: planId ?? "" };
|
|
4544
|
+
if (stored && stored.base !== null && planId !== null && binding.planId !== "" && planId !== binding.planId && stored.pending?.kind !== "pull") {
|
|
4545
|
+
return halted({ reason: "binding-mismatch", expected: binding.planId, actual: planId });
|
|
4546
|
+
}
|
|
4547
|
+
target.remoteId = binding.remoteId;
|
|
4548
|
+
const state = stored ?? { generation: 0, epoch: null, base: null, pending: null };
|
|
4549
|
+
const remoteOptions = { server, remoteId: binding.remoteId, token: options.token, fetch: doFetch };
|
|
4550
|
+
const { remote, account } = await fetchRemote(remoteOptions);
|
|
4551
|
+
if (options.confirmAccount !== void 0 && options.confirmAccount !== account) return halted({ reason: "account-mismatch", bound: options.confirmAccount, actual: account });
|
|
4552
|
+
if (stored) {
|
|
4553
|
+
if (stored.binding.account === void 0) {
|
|
4554
|
+
if (options.confirmAccount !== account) return halted({ reason: "account-unconfirmed", account });
|
|
4555
|
+
save2({ ...state, generation: state.generation + 1 }, { ...stored.binding, account });
|
|
4556
|
+
continue;
|
|
4557
|
+
}
|
|
4558
|
+
if (stored.binding.account !== account) return halted({ reason: "account-mismatch", bound: stored.binding.account, actual: account });
|
|
4559
|
+
} else binding = { ...binding, account };
|
|
4560
|
+
const bindingId = bindingIdOf(store.dir, binding.remoteId, account);
|
|
4561
|
+
if (state.pending?.kind === "pull" && options.recover && round === 0) {
|
|
4562
|
+
try {
|
|
4563
|
+
const next = pullRecovered(state, options.recover, local?.hash ?? null, remoteMark(remote), bindingId, hashOf);
|
|
4564
|
+
save2(next, options.recover.applied ? written(binding) : notWritten(binding));
|
|
4565
|
+
continue;
|
|
4566
|
+
} catch (e) {
|
|
4567
|
+
if (!(e instanceof SyncStateError)) throw e;
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
const input = {
|
|
4571
|
+
// (印は、サーバーの場所・ファイル・サーバー側の計画の ID に結び付ける。ある計画を見て得た選択を、別の計画には使えない)
|
|
4572
|
+
state,
|
|
4573
|
+
local,
|
|
4574
|
+
remote,
|
|
4575
|
+
bindingId,
|
|
4576
|
+
baseText: state.base ? store.getObject(state.base.hash) : null,
|
|
4577
|
+
now: now2(),
|
|
4578
|
+
hashOf,
|
|
4579
|
+
approvedDeletion: options.approvedDeletion,
|
|
4580
|
+
restoreDeletion: options.restoreDeletion,
|
|
4581
|
+
resolution: options.resolution,
|
|
4582
|
+
firstLink: options.firstLink
|
|
4583
|
+
};
|
|
4584
|
+
const decision2 = decide(input);
|
|
4585
|
+
await options.onStep?.(decision2.kind);
|
|
4586
|
+
if (round === 0 && decision2.kind !== "halt") {
|
|
4587
|
+
const used = "usedChoice" in decision2 ? decision2.usedChoice : void 0;
|
|
4588
|
+
if (options.firstLink && used !== "firstLink") return halted({ reason: "choice-not-applied", choice: "firstLink" });
|
|
4589
|
+
if (options.resolution && used !== "resolution") return halted({ reason: "choice-not-applied", choice: "resolution" });
|
|
4590
|
+
}
|
|
4591
|
+
switch (decision2.kind) {
|
|
4592
|
+
case "noop":
|
|
4593
|
+
return { status: "synced", pulled, pushed, edited, revision: state.base?.revision ?? null, localHash: local?.hash ?? null };
|
|
4594
|
+
case "halt":
|
|
4595
|
+
if (decision2.halt.reason === "recover-pull" && remote.kind === "present") {
|
|
4596
|
+
const preview = previewRecovery(input, (hash) => store.getObject(hash));
|
|
4597
|
+
const mismatch = (id, baseExists) => baseExists && planId !== null && id !== "" && planId !== id;
|
|
4598
|
+
const stop = (_outcome) => ({ next: "binding-mismatch", localChanges: [], remoteChanges: [] });
|
|
4599
|
+
const applied = mismatch(binding.pendingPlanId ?? binding.planId, true) ? stop(preview.applied) : preview.applied;
|
|
4600
|
+
const notApplied = mismatch(binding.planId, state.base !== null) ? stop(preview.notApplied) : preview.notApplied;
|
|
4601
|
+
return halted(decision2.halt, { recovery: { token: recoveryToken2(state, local?.hash ?? null, remoteMark(remote), bindingId), applied, notApplied } });
|
|
4602
|
+
}
|
|
4603
|
+
return halted(decision2.halt);
|
|
4604
|
+
case "edit-local":
|
|
4605
|
+
try {
|
|
4606
|
+
commitFile(file, decision2.write.text, `"${decision2.expectedLocal}"`);
|
|
4607
|
+
edited++;
|
|
4608
|
+
} catch (e) {
|
|
4609
|
+
if (e instanceof FileBusy) return { status: "busy", what: "file" };
|
|
4610
|
+
if (!(e instanceof FileConflict)) throw e;
|
|
4611
|
+
}
|
|
4612
|
+
break;
|
|
4613
|
+
case "set-base":
|
|
4614
|
+
for (const c of decision2.keep) store.putObject(c.text);
|
|
4615
|
+
save2(baseSet(state, decision2.epoch, decision2.base), state.base === null ? { ...binding, planId: planId ?? "" } : binding);
|
|
4616
|
+
break;
|
|
4617
|
+
case "finish-pull":
|
|
4618
|
+
save2(pullWritten(state, decision2.pending), written(binding));
|
|
4619
|
+
break;
|
|
4620
|
+
case "pull": {
|
|
4621
|
+
for (const c of decision2.keep) store.putObject(c.text);
|
|
4622
|
+
if (decision2.backup && local !== null) copyFileSync(file, `${file}.before-sync-${local.hash.slice(0, 8)}.json`);
|
|
4623
|
+
const pullBinding = state.base === null ? { ...binding, pendingPlanId: planIdOf(decision2.write.text) ?? "" } : binding;
|
|
4624
|
+
save2(recordPending(state, decision2.epoch, decision2.pending), pullBinding);
|
|
4625
|
+
await options.onStep?.("pull:recorded");
|
|
4626
|
+
try {
|
|
4627
|
+
commitFile(file, decision2.write.text, local === null ? null : revisionOf(local.text));
|
|
4628
|
+
} catch (e) {
|
|
4629
|
+
if (e instanceof FileConflict) {
|
|
4630
|
+
save2(pullNotWritten(stored, decision2.pending), notWritten(pullBinding));
|
|
4631
|
+
break;
|
|
4632
|
+
}
|
|
4633
|
+
if (e instanceof FileBusy) {
|
|
4634
|
+
save2(pullNotWritten(stored, decision2.pending), notWritten(pullBinding));
|
|
4635
|
+
return { status: "busy", what: "file" };
|
|
4636
|
+
}
|
|
4637
|
+
throw e;
|
|
4638
|
+
}
|
|
4639
|
+
await options.onStep?.("pull:written");
|
|
4640
|
+
save2(pullWritten(stored, decision2.pending), written(pullBinding));
|
|
4641
|
+
pulled++;
|
|
4642
|
+
break;
|
|
4643
|
+
}
|
|
4644
|
+
case "push":
|
|
4645
|
+
case "resend": {
|
|
4646
|
+
let pending;
|
|
4647
|
+
if (decision2.kind === "push") {
|
|
4648
|
+
store.putObject(decision2.content.text);
|
|
4649
|
+
pending = { kind: "push", opId: randomUUID3(), hash: decision2.content.hash, expected: decision2.expected, at: now2().toISOString() };
|
|
4650
|
+
save2(recordPending(state, decision2.epoch, pending), state.base === null ? { ...binding, planId: planId ?? "" } : binding);
|
|
4651
|
+
} else pending = decision2.pending;
|
|
4652
|
+
if (pending.rejected) {
|
|
4653
|
+
const op = await settleOperation({ ...remoteOptions, epoch: stored.epoch ?? "" }, pending.opId);
|
|
4654
|
+
if ("history" in op) return halted({ reason: "history-changed", expected: stored.epoch ?? "", actual: op.history });
|
|
4655
|
+
if (op.account !== account) return halted({ reason: "account-mismatch", bound: account, actual: op.account });
|
|
4656
|
+
if (op.revision !== null) {
|
|
4657
|
+
save2(pushAccepted(stored, pending.opId, op.revision), stored.binding);
|
|
4658
|
+
pushed++;
|
|
4659
|
+
} else save2(pushRejected(stored, pending.opId), stored.binding);
|
|
4660
|
+
break;
|
|
4661
|
+
}
|
|
4662
|
+
const text2 = store.getObject(pending.hash);
|
|
4663
|
+
if (text2 === null) throw new Error("the content of a pending push is missing from the sync state folder");
|
|
4664
|
+
let result;
|
|
4665
|
+
try {
|
|
4666
|
+
result = await sendPush({ ...remoteOptions, epoch: stored.epoch }, pending, text2);
|
|
4667
|
+
} catch (e) {
|
|
4668
|
+
if (e instanceof SyncRejectedError) save2(pushRefused(stored, pending.opId, e.status, now2().toISOString()), stored.binding);
|
|
4669
|
+
throw e;
|
|
4670
|
+
}
|
|
4671
|
+
await options.onStep?.("push:sent");
|
|
4672
|
+
if ("history" in result) return halted({ reason: "history-changed", expected: stored.epoch ?? "", actual: result.history });
|
|
4673
|
+
if ("deleted" in result) return halted({ reason: "remote-deleted" });
|
|
4674
|
+
if ("accepted" in result) {
|
|
4675
|
+
save2(pushAccepted(stored, pending.opId, result.accepted), stored.binding);
|
|
4676
|
+
pushed++;
|
|
4677
|
+
} else save2(pushRejected(stored, pending.opId), stored.binding);
|
|
4678
|
+
break;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
return { status: "busy", what: "sync" };
|
|
4683
|
+
} finally {
|
|
4684
|
+
unlock?.();
|
|
4685
|
+
unlockFile();
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
function written(binding) {
|
|
4689
|
+
const { pendingPlanId, ...rest } = binding;
|
|
4690
|
+
return pendingPlanId === void 0 ? rest : { ...rest, planId: pendingPlanId };
|
|
4691
|
+
}
|
|
4692
|
+
function notWritten(binding) {
|
|
4693
|
+
const { pendingPlanId: _dropped, ...rest } = binding;
|
|
4694
|
+
return rest;
|
|
4695
|
+
}
|
|
4696
|
+
function planIdOf(text2) {
|
|
4697
|
+
try {
|
|
4698
|
+
const id = JSON.parse(text2).id;
|
|
4699
|
+
return typeof id === "string" ? id : null;
|
|
4700
|
+
} catch {
|
|
4701
|
+
return null;
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
// cli/sync/credentials.ts
|
|
4706
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4707
|
+
import { closeSync as closeSync2, existsSync as existsSync6, fstatSync, linkSync, lstatSync, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync6, renameSync as renameSync3, rmSync as rmSync3, statSync as statSync3, writeSync, fsyncSync as fsyncSync2 } from "node:fs";
|
|
4708
|
+
import { join as join5 } from "node:path";
|
|
4709
|
+
var CredentialsUnsafe = class extends Error {
|
|
4710
|
+
constructor(reason) {
|
|
4711
|
+
super(`cannot store credentials safely: ${reason}`);
|
|
4712
|
+
this.reason = reason;
|
|
4713
|
+
}
|
|
4714
|
+
reason;
|
|
4715
|
+
};
|
|
4716
|
+
var CredentialsBusy = class extends Error {
|
|
4717
|
+
constructor() {
|
|
4718
|
+
super("another login or logout is running");
|
|
4719
|
+
}
|
|
4720
|
+
};
|
|
4721
|
+
var dir = () => join5(configDir(), "credentials");
|
|
4722
|
+
var credentialsPath = (server) => join5(dir(), hashOf(normalizeServer(server)).slice(0, 32) + ".json");
|
|
4723
|
+
function unsafeReason(path, kind) {
|
|
4724
|
+
const st = lstatSync(path);
|
|
4725
|
+
if (st.isSymbolicLink()) return `${path} is a symbolic link`;
|
|
4726
|
+
if (kind === "dir" ? !st.isDirectory() : !st.isFile()) return `${path} is not a ${kind === "dir" ? "directory" : "regular file"}`;
|
|
4727
|
+
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return `${path} is owned by another user`;
|
|
4728
|
+
if ((st.mode & 63) !== 0) return `${path} is accessible by other users (mode ${(st.mode & 511).toString(8)})`;
|
|
4729
|
+
return null;
|
|
4730
|
+
}
|
|
4731
|
+
function ensureDir() {
|
|
4732
|
+
if (process.platform === "win32") throw new CredentialsUnsafe("storing credentials on Windows is not supported yet");
|
|
4733
|
+
mkdirSync4(dir(), { recursive: true, mode: 448 });
|
|
4734
|
+
const reason = unsafeReason(dir(), "dir");
|
|
4735
|
+
if (reason) throw new CredentialsUnsafe(reason);
|
|
4736
|
+
}
|
|
4737
|
+
async function withCredentialsLock(server, fn) {
|
|
4738
|
+
ensureDir();
|
|
4739
|
+
let unlock;
|
|
4740
|
+
try {
|
|
4741
|
+
unlock = lockFile(credentialsPath(server), { waitMs: 0 });
|
|
4742
|
+
} catch (e) {
|
|
4743
|
+
if (e instanceof FileBusy) throw new CredentialsBusy();
|
|
4744
|
+
throw e;
|
|
4745
|
+
}
|
|
4746
|
+
try {
|
|
4747
|
+
return await fn();
|
|
4748
|
+
} finally {
|
|
4749
|
+
unlock();
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
function inspectCredentials(server) {
|
|
4753
|
+
const path = credentialsPath(server);
|
|
4754
|
+
try {
|
|
4755
|
+
lstatSync(path);
|
|
4756
|
+
} catch (e) {
|
|
4757
|
+
if (e.code === "ENOENT") return { kind: "absent" };
|
|
4758
|
+
return { kind: "unusable", path, reason: String(e) };
|
|
4759
|
+
}
|
|
4760
|
+
try {
|
|
4761
|
+
if (process.platform !== "win32") {
|
|
4762
|
+
const reason = unsafeReason(dir(), "dir") ?? unsafeReason(path, "file");
|
|
4763
|
+
if (reason) return { kind: "unusable", path, reason };
|
|
4764
|
+
}
|
|
4765
|
+
const text2 = readFileSync6(path, "utf8");
|
|
4766
|
+
let value;
|
|
4767
|
+
try {
|
|
4768
|
+
value = JSON.parse(text2);
|
|
4769
|
+
} catch {
|
|
4770
|
+
return { kind: "unusable", path, reason: "the file is not valid JSON" };
|
|
4771
|
+
}
|
|
4772
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return { kind: "unusable", path, reason: "the file is not a credentials file" };
|
|
4773
|
+
if (value.server !== normalizeServer(server)) return { kind: "unusable", path, reason: "the file belongs to another server" };
|
|
4774
|
+
if (typeof value.token !== "string" || typeof value.account !== "string" || typeof value.login !== "string") return { kind: "unusable", path, reason: "the file is not a credentials file" };
|
|
4775
|
+
return { kind: "valid", credentials: { server: value.server, account: value.account, login: value.login, token: value.token, createdAt: String(value.createdAt ?? "") } };
|
|
4776
|
+
} catch (e) {
|
|
4777
|
+
return { kind: "unusable", path, reason: `the file could not be read (${e.code ?? "error"})` };
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
function readCredentials(server) {
|
|
4781
|
+
const stored = inspectCredentials(server);
|
|
4782
|
+
return stored.kind === "valid" ? stored.credentials : null;
|
|
4783
|
+
}
|
|
4784
|
+
var CredentialsSaveFailed = class extends Error {
|
|
4785
|
+
constructor(reason, restored, backup, unsafe) {
|
|
4786
|
+
super(`could not store credentials: ${reason}`);
|
|
4787
|
+
this.reason = reason;
|
|
4788
|
+
this.restored = restored;
|
|
4789
|
+
this.backup = backup;
|
|
4790
|
+
this.unsafe = unsafe;
|
|
4791
|
+
}
|
|
4792
|
+
reason;
|
|
4793
|
+
restored;
|
|
4794
|
+
backup;
|
|
4795
|
+
unsafe;
|
|
4796
|
+
};
|
|
4797
|
+
function saveCredentials(credentials, hooks = {}) {
|
|
4798
|
+
const path = credentialsPath(credentials.server);
|
|
4799
|
+
const op = randomUUID4();
|
|
4800
|
+
const temp = `${path}.${op}.tmp`, prev = `${path}.prev-${op}`;
|
|
4801
|
+
const text2 = JSON.stringify({ ...credentials, server: normalizeServer(credentials.server) }, null, 2) + "\n";
|
|
4802
|
+
const message = (e) => e instanceof CredentialsUnsafe ? e.reason : e instanceof Error ? e.message : String(e);
|
|
4803
|
+
const quiet = (fn) => {
|
|
4804
|
+
try {
|
|
4805
|
+
fn();
|
|
4806
|
+
} catch {
|
|
4807
|
+
}
|
|
4808
|
+
};
|
|
4809
|
+
try {
|
|
4810
|
+
ensureDir();
|
|
4811
|
+
const fd = openSync2(temp, "wx", 384);
|
|
4812
|
+
try {
|
|
4813
|
+
const st = fstatSync(fd);
|
|
4814
|
+
if ((st.mode & 63) !== 0 || typeof process.getuid === "function" && st.uid !== process.getuid()) throw new CredentialsUnsafe(`${temp} is not private`);
|
|
4815
|
+
writeSync(fd, text2);
|
|
4816
|
+
fsyncSync2(fd);
|
|
4817
|
+
} finally {
|
|
4818
|
+
closeSync2(fd);
|
|
4819
|
+
}
|
|
4820
|
+
} catch (e) {
|
|
4821
|
+
quiet(() => rmSync3(temp, { force: true }));
|
|
4822
|
+
throw new CredentialsSaveFailed(message(e), true, null, false);
|
|
4823
|
+
}
|
|
4824
|
+
let hadPrevious = false;
|
|
4825
|
+
try {
|
|
4826
|
+
hadPrevious = existsSync6(path);
|
|
4827
|
+
if (hadPrevious) linkSync(path, prev);
|
|
4828
|
+
renameSync3(temp, path);
|
|
4829
|
+
} catch (e) {
|
|
4830
|
+
quiet(() => rmSync3(temp, { force: true }));
|
|
4831
|
+
quiet(() => rmSync3(prev, { force: true }));
|
|
4832
|
+
throw new CredentialsSaveFailed(message(e), true, null, false);
|
|
4833
|
+
}
|
|
4834
|
+
let reason, unsafe = false;
|
|
4835
|
+
try {
|
|
4836
|
+
hooks.afterPlace?.();
|
|
4837
|
+
reason = unsafeReason(path, "file");
|
|
4838
|
+
unsafe = reason !== null;
|
|
4839
|
+
if (reason === null && readFileSync6(path, "utf8") !== text2) reason = "the stored file is not what was written";
|
|
4840
|
+
} catch (e) {
|
|
4841
|
+
reason = message(e);
|
|
4842
|
+
}
|
|
4843
|
+
if (reason === null) {
|
|
4844
|
+
quiet(() => rmSync3(prev, { force: true }));
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4847
|
+
let restored = false;
|
|
4848
|
+
try {
|
|
4849
|
+
if (readFileSync6(path, "utf8") === text2) {
|
|
4850
|
+
if (hadPrevious) renameSync3(prev, path);
|
|
4851
|
+
else rmSync3(path, { force: true });
|
|
4852
|
+
restored = true;
|
|
4853
|
+
}
|
|
4854
|
+
} catch {
|
|
4855
|
+
}
|
|
4856
|
+
throw new CredentialsSaveFailed(reason, restored, !restored && hadPrevious ? prev : null, unsafe);
|
|
4857
|
+
}
|
|
4858
|
+
function removeCredentials(server) {
|
|
4859
|
+
const path = credentialsPath(server);
|
|
4860
|
+
if (!existsSync6(path)) return false;
|
|
4861
|
+
rmSync3(path, { force: true });
|
|
4862
|
+
return true;
|
|
4863
|
+
}
|
|
4864
|
+
function resolveToken(server) {
|
|
4865
|
+
if (process.env.BOXGLOW_TOKEN) return { token: process.env.BOXGLOW_TOKEN, source: "env" };
|
|
4866
|
+
const saved = readCredentials(server);
|
|
4867
|
+
return saved ? { token: saved.token, source: "file", login: saved.login } : null;
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
// cli/sync/login.ts
|
|
4871
|
+
import { hostname as hostname2 } from "node:os";
|
|
4872
|
+
function serverProblem(server) {
|
|
4873
|
+
let url;
|
|
4874
|
+
try {
|
|
4875
|
+
url = new URL(server);
|
|
4876
|
+
} catch {
|
|
4877
|
+
return t("\u30B5\u30FC\u30D0\u30FC\u306E\u5834\u6240\u304C URL \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093: {server}", { server });
|
|
4878
|
+
}
|
|
4879
|
+
if (url.protocol === "https:") return null;
|
|
4880
|
+
if (url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) return null;
|
|
4881
|
+
return t("https \u3067\u306F\u306A\u3044\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u307E\u305B\u3093 (\u624B\u5143\u306E localhost \u3092\u9664\u304F): {server}", { server });
|
|
4882
|
+
}
|
|
4883
|
+
var headers2 = (token) => ({ ...token ? { authorization: `Bearer ${token}` } : {}, "content-type": "application/json", "x-boxglow-version": APP_VERSION });
|
|
4884
|
+
async function revoke(server, token, doFetch) {
|
|
4885
|
+
try {
|
|
4886
|
+
const res = await doFetch(`${server}/v1/tokens/current`, { method: "DELETE", headers: headers2(token), redirect: "error" });
|
|
4887
|
+
return res.status === 200 || res.status === 401;
|
|
4888
|
+
} catch {
|
|
4889
|
+
return false;
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
async function runLogin(o) {
|
|
4893
|
+
const { out: out2 } = o;
|
|
4894
|
+
const doFetch = o.fetch ?? fetch;
|
|
4895
|
+
const sleep = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
4896
|
+
const problem = serverProblem(o.server);
|
|
4897
|
+
if (problem) {
|
|
4898
|
+
out2(problem);
|
|
4899
|
+
return 1;
|
|
4900
|
+
}
|
|
4901
|
+
const server = normalizeServer(o.server);
|
|
4902
|
+
try {
|
|
4903
|
+
return await withCredentialsLock(server, async () => {
|
|
4904
|
+
const stored = inspectCredentials(server);
|
|
4905
|
+
if (stored.kind === "unusable") {
|
|
4906
|
+
out2(t("\u4FDD\u5B58\u6E08\u307F\u306E\u8CC7\u683C\u60C5\u5831\u304C\u3042\u308A\u307E\u3059\u304C\u3001\u5B89\u5168\u306B\u8AAD\u3081\u307E\u305B\u3093: {reason}", { reason: stored.reason }));
|
|
4907
|
+
out2(t("\u8AB0\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304B\u3092\u78BA\u304B\u3081\u3089\u308C\u306A\u3044\u306E\u3067\u3001\u7F6E\u304D\u63DB\u3048\u307E\u305B\u3093\u3002\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u304B\u3081\u3066\u3001\u76F4\u3059\u304B\u3001\u6D88\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044: {path}", { path: stored.path }));
|
|
4908
|
+
return 1;
|
|
4909
|
+
}
|
|
4910
|
+
const previous = stored.kind === "valid" ? stored.credentials : null;
|
|
4911
|
+
let started;
|
|
4912
|
+
try {
|
|
4913
|
+
const res = await doFetch(`${server}/v1/auth/device`, { method: "POST", headers: headers2(), redirect: "error" });
|
|
4914
|
+
if (res.status === 429) {
|
|
4915
|
+
out2(t("\u30B5\u30A4\u30F3\u30A4\u30F3\u306E\u8981\u6C42\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u5C11\u3057\u5F85\u3063\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4916
|
+
return 1;
|
|
4917
|
+
}
|
|
4918
|
+
if (res.status === 503) {
|
|
4919
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u306F\u4FDD\u5B88\u4E2D\u3067\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4920
|
+
return 1;
|
|
4921
|
+
}
|
|
4922
|
+
if (!res.ok) {
|
|
4923
|
+
out2(t("\u30B5\u30A4\u30F3\u30A4\u30F3\u3092\u59CB\u3081\u3089\u308C\u307E\u305B\u3093\u3067\u3057\u305F (\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54: {status})", { status: res.status }));
|
|
4924
|
+
return 1;
|
|
4925
|
+
}
|
|
4926
|
+
started = await res.json();
|
|
4927
|
+
if (typeof started.device_code !== "string" || typeof started.user_code !== "string" || typeof started.verification_uri !== "string") throw new Error("bad response");
|
|
4928
|
+
} catch (e) {
|
|
4929
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: {message}", { message: e instanceof Error ? e.message : String(e) }));
|
|
4930
|
+
return 1;
|
|
4931
|
+
}
|
|
4932
|
+
out2(t("\u30D6\u30E9\u30A6\u30B6\u3067\u6B21\u306E\u30DA\u30FC\u30B8\u3092\u958B\u304D\u3001\u30B3\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 (GitHub \u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u8A31\u53EF\u3057\u307E\u3059):"));
|
|
4933
|
+
out2(` ${started.verification_uri}`);
|
|
4934
|
+
out2(t(" \u30B3\u30FC\u30C9: {code}", { code: started.user_code }));
|
|
4935
|
+
out2(t("\u8A31\u53EF\u3055\u308C\u308B\u306E\u3092\u5F85\u3063\u3066\u3044\u307E\u3059... (\u4E2D\u6B62\u306F Ctrl+C)"));
|
|
4936
|
+
let interval = Math.max(1, Number(started.interval) || 5);
|
|
4937
|
+
const deadline = Date.now() + Math.max(1, Number(started.expires_in) || 900) * 1e3;
|
|
4938
|
+
let issued = null;
|
|
4939
|
+
while (issued === null) {
|
|
4940
|
+
await sleep(interval * 1e3);
|
|
4941
|
+
if (Date.now() > deadline) {
|
|
4942
|
+
out2(t("\u30B3\u30FC\u30C9\u306E\u671F\u9650\u304C\u5207\u308C\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6 boxglow login \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4943
|
+
return 1;
|
|
4944
|
+
}
|
|
4945
|
+
let res;
|
|
4946
|
+
try {
|
|
4947
|
+
res = await doFetch(`${server}/v1/auth/device/token`, {
|
|
4948
|
+
method: "POST",
|
|
4949
|
+
headers: headers2(),
|
|
4950
|
+
redirect: "error",
|
|
4951
|
+
body: JSON.stringify({ device_code: started.device_code, device_name: (o.deviceName ?? hostname2()).slice(0, 64) })
|
|
4952
|
+
});
|
|
4953
|
+
} catch {
|
|
4954
|
+
continue;
|
|
4955
|
+
}
|
|
4956
|
+
if (res.status === 429) {
|
|
4957
|
+
interval = Math.max(interval, Number(res.headers.get("retry-after")) || 60);
|
|
4958
|
+
continue;
|
|
4959
|
+
}
|
|
4960
|
+
if (res.status === 502 || res.status === 503) continue;
|
|
4961
|
+
const body = await res.json().catch(() => ({}));
|
|
4962
|
+
if (res.status === 200 && body.status === "pending") continue;
|
|
4963
|
+
if (res.status === 200 && body.status === "slow_down") {
|
|
4964
|
+
interval = Math.max(interval + 5, Number(body.interval) || 0);
|
|
4965
|
+
continue;
|
|
4966
|
+
}
|
|
4967
|
+
if (res.status === 200 && body.status === "ok" && typeof body.token === "string" && typeof body.account === "string") {
|
|
4968
|
+
issued = { token: body.token, account: body.account, login: String(body.login ?? "") };
|
|
4969
|
+
break;
|
|
4970
|
+
}
|
|
4971
|
+
if (res.status === 403 && body.code === "not-invited") {
|
|
4972
|
+
out2(t("\u3053\u306E GitHub \u306E\u30A2\u30AB\u30A6\u30F3\u30C8 ({login}) \u306F\u3001\u307E\u3060\u62DB\u5F85\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u540C\u671F\u306F\u3001\u62DB\u5F85\u3057\u305F\u5229\u7528\u8005\u3060\u3051\u304C\u4F7F\u3048\u307E\u3059", { login: String(body.login ?? "?") }));
|
|
4973
|
+
if (typeof body.waitlist === "string") out2(t("\u9806\u756A\u5F85\u3061\u306E\u767B\u9332: {url}", { url: body.waitlist }));
|
|
4974
|
+
return 1;
|
|
4975
|
+
}
|
|
4976
|
+
if (res.status === 403 && body.code === "suspended") {
|
|
4977
|
+
out2(t("\u3053\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u306F\u3001\u6B62\u3081\u3089\u308C\u3066\u3044\u307E\u3059"));
|
|
4978
|
+
return 1;
|
|
4979
|
+
}
|
|
4980
|
+
if (res.status === 403 && body.code === "too-many-devices") {
|
|
4981
|
+
out2(t("\u30B5\u30A4\u30F3\u30A4\u30F3\u6E08\u307F\u306E\u7AEF\u672B\u304C\u4E0A\u9650\u306B\u5C4A\u3044\u3066\u3044\u307E\u3059\u3002\u4F7F\u3063\u3066\u3044\u306A\u3044\u7AEF\u672B\u3067 boxglow logout \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u8A66\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4982
|
+
return 1;
|
|
4983
|
+
}
|
|
4984
|
+
if (body.code === "access_denied") {
|
|
4985
|
+
out2(t("\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F (\u30D6\u30E9\u30A6\u30B6\u3067\u62D2\u5426\u3055\u308C\u307E\u3057\u305F)"));
|
|
4986
|
+
return 1;
|
|
4987
|
+
}
|
|
4988
|
+
if (body.code === "expired_token") {
|
|
4989
|
+
out2(t("\u30B3\u30FC\u30C9\u306E\u671F\u9650\u304C\u5207\u308C\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6 boxglow login \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4990
|
+
return 1;
|
|
4991
|
+
}
|
|
4992
|
+
out2(t("\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F (\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54: {status} {code})", { status: res.status, code: String(body.code ?? "") }));
|
|
4993
|
+
return 1;
|
|
4994
|
+
}
|
|
4995
|
+
if (previous && previous.account !== issued.account) {
|
|
4996
|
+
const revoked = await revoke(server, issued.token, doFetch);
|
|
4997
|
+
out2(t("\u3053\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u3001\u3059\u3067\u306B {current} \u3068\u3057\u3066\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u3059\u3002{next} \u306B\u66FF\u3048\u308B\u306B\u306F\u3001\u5148\u306B boxglow logout \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044", { current: previous.login, next: issued.login }));
|
|
4998
|
+
if (!revoked) out2(t("(\u4ECA\u56DE\u767A\u884C\u3055\u308C\u305F\u30C8\u30FC\u30AF\u30F3\u3092\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u4F7F\u308F\u308C\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u304C\u6B8B\u3063\u3066\u3044\u307E\u3059)"));
|
|
4999
|
+
return 1;
|
|
5000
|
+
}
|
|
5001
|
+
try {
|
|
5002
|
+
saveCredentials({ server, account: issued.account, login: issued.login, token: issued.token, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5003
|
+
} catch (e) {
|
|
5004
|
+
const revoked = await revoke(server, issued.token, doFetch);
|
|
5005
|
+
const failed = e instanceof CredentialsSaveFailed ? e : null;
|
|
5006
|
+
out2(t("\u8CC7\u683C\u60C5\u5831\u3092\u3001\u5B89\u5168\u306B\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: {reason}", { reason: failed ? failed.reason : e instanceof Error ? e.message : String(e) }));
|
|
5007
|
+
if (failed && !failed.restored) out2(failed.backup ? t("\u4ECA\u307E\u3067\u306E\u8CC7\u683C\u60C5\u5831\u3092\u3001\u5143\u306E\u5834\u6240\u306B\u623B\u305B\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u63A7\u3048\u306F\u3053\u3053\u306B\u3042\u308A\u307E\u3059: {path}", { path: failed.backup }) : t("\u8CC7\u683C\u60C5\u5831\u306E\u7F6E\u304D\u5834\u306B\u3001\u4ECA\u56DE\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u6B8B\u3063\u3066\u3044\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044"));
|
|
5008
|
+
out2(revoked ? t("\u4ECA\u56DE\u767A\u884C\u3055\u308C\u305F\u30C8\u30FC\u30AF\u30F3\u306F\u3001\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F\u3002") : t("\u4ECA\u56DE\u767A\u884C\u3055\u308C\u305F\u30C8\u30FC\u30AF\u30F3\u3092\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3067\u3057\u305F (\u307E\u3060\u6709\u52B9\u304B\u3082\u3057\u308C\u307E\u305B\u3093)\u3002"));
|
|
5009
|
+
out2(t("\u4FDD\u5B58\u305B\u305A\u306B\u4F7F\u3046\u306B\u306F\u3001\u30C8\u30FC\u30AF\u30F3\u3092\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u3067\u6E21\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5010
|
+
return 1;
|
|
5011
|
+
}
|
|
5012
|
+
if (previous && previous.token !== issued.token) await revoke(server, previous.token, doFetch);
|
|
5013
|
+
out2(t("\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u307E\u3057\u305F: {login} ({account})", { login: issued.login, account: issued.account }));
|
|
5014
|
+
if (process.env.BOXGLOW_TOKEN) out2(t("\u6CE8\u610F: \u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u540C\u671F\u3067\u306F\u3001\u4FDD\u5B58\u3057\u305F\u30B5\u30A4\u30F3\u30A4\u30F3\u3088\u308A\u3001\u305D\u3061\u3089\u304C\u512A\u5148\u3055\u308C\u307E\u3059"));
|
|
5015
|
+
return 0;
|
|
5016
|
+
});
|
|
5017
|
+
} catch (e) {
|
|
5018
|
+
if (e instanceof CredentialsBusy) {
|
|
5019
|
+
out2(t("\u307B\u304B\u306E boxglow login / logout \u304C\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u7D42\u308F\u3063\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5020
|
+
return 1;
|
|
5021
|
+
}
|
|
5022
|
+
if (e instanceof CredentialsUnsafe) {
|
|
5023
|
+
out2(t("\u8CC7\u683C\u60C5\u5831\u3092\u3001\u5B89\u5168\u306B\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093: {reason}", { reason: e.reason }));
|
|
5024
|
+
out2(t("\u4FDD\u5B58\u305B\u305A\u306B\u4F7F\u3046\u306B\u306F\u3001\u30C8\u30FC\u30AF\u30F3\u3092\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u3067\u6E21\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5025
|
+
return 1;
|
|
5026
|
+
}
|
|
5027
|
+
throw e;
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
async function runLogout(o) {
|
|
5031
|
+
const { out: out2 } = o;
|
|
5032
|
+
const doFetch = o.fetch ?? fetch;
|
|
5033
|
+
const server = normalizeServer(o.server);
|
|
5034
|
+
try {
|
|
5035
|
+
return await withCredentialsLock(server, async () => {
|
|
5036
|
+
const stored = inspectCredentials(server);
|
|
5037
|
+
if (stored.kind === "unusable") {
|
|
5038
|
+
out2(t("\u4FDD\u5B58\u6E08\u307F\u306E\u8CC7\u683C\u60C5\u5831\u304C\u3042\u308A\u307E\u3059\u304C\u3001\u5B89\u5168\u306B\u8AAD\u3081\u307E\u305B\u3093: {reason}", { reason: stored.reason }));
|
|
5039
|
+
out2(t("\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u306F\u3001\u3067\u304D\u3066\u3044\u307E\u305B\u3093 (\u30D5\u30A1\u30A4\u30EB\u304C\u6B8B\u3063\u3066\u3044\u3066\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u30C8\u30FC\u30AF\u30F3\u3082\u6709\u52B9\u306A\u307E\u307E\u304B\u3082\u3057\u308C\u307E\u305B\u3093)\u3002\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044: {path}", { path: stored.path }));
|
|
5040
|
+
return 1;
|
|
5041
|
+
}
|
|
5042
|
+
const saved = stored.kind === "valid" ? stored.credentials : null;
|
|
5043
|
+
if (!saved) {
|
|
5044
|
+
out2(t("\u3053\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u305B\u3093"));
|
|
5045
|
+
} else {
|
|
5046
|
+
const revoked = serverProblem(server) === null && await revoke(server, saved.token, doFetch);
|
|
5047
|
+
removeCredentials(server);
|
|
5048
|
+
out2(revoked ? t("\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F (\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u30C8\u30FC\u30AF\u30F3\u3082\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F)") : t("\u624B\u5143\u306E\u8CC7\u683C\u60C5\u5831\u3092\u6D88\u3057\u307E\u3057\u305F\u3002\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u306A\u304B\u3063\u305F\u306E\u3067\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u30C8\u30FC\u30AF\u30F3\u306F\u53D6\u308A\u6D88\u305B\u3066\u3044\u307E\u305B\u3093 (\u671F\u9650\u307E\u3067\u6709\u52B9\u3067\u3059)"));
|
|
5049
|
+
}
|
|
5050
|
+
if (process.env.BOXGLOW_TOKEN) out2(t("\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u306F\u3001\u3053\u306E\u64CD\u4F5C\u3067\u306F\u53D6\u308A\u6D88\u3055\u308C\u307E\u305B\u3093 (\u540C\u671F\u3067\u306F\u3001\u5F15\u304D\u7D9A\u304D\u305D\u306E\u30C8\u30FC\u30AF\u30F3\u304C\u4F7F\u308F\u308C\u307E\u3059)"));
|
|
5051
|
+
return 0;
|
|
5052
|
+
});
|
|
5053
|
+
} catch (e) {
|
|
5054
|
+
if (e instanceof CredentialsBusy) {
|
|
5055
|
+
out2(t("\u307B\u304B\u306E boxglow login / logout \u304C\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u7D42\u308F\u3063\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5056
|
+
return 1;
|
|
5057
|
+
}
|
|
5058
|
+
if (e instanceof CredentialsUnsafe) {
|
|
5059
|
+
out2(t("\u8CC7\u683C\u60C5\u5831\u306E\u7F6E\u304D\u5834\u3092\u3001\u5B89\u5168\u306B\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093: {reason}", { reason: e.reason }));
|
|
5060
|
+
out2(t("\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u306F\u3001\u3067\u304D\u3066\u3044\u307E\u305B\u3093 (\u8CC7\u683C\u60C5\u5831\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u6B8B\u3063\u3066\u3044\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093)"));
|
|
5061
|
+
return 1;
|
|
5062
|
+
}
|
|
5063
|
+
throw e;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
async function runWhoami(o) {
|
|
5067
|
+
const { out: out2 } = o;
|
|
5068
|
+
const server = normalizeServer(o.server);
|
|
5069
|
+
const problem = serverProblem(server);
|
|
5070
|
+
if (problem) {
|
|
5071
|
+
out2(problem);
|
|
5072
|
+
return 1;
|
|
5073
|
+
}
|
|
5074
|
+
const resolved = resolveToken(server);
|
|
5075
|
+
if (!resolved) {
|
|
5076
|
+
out2(t("\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u305B\u3093\u3002boxglow login --server {server} \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044", { server }));
|
|
5077
|
+
return 1;
|
|
5078
|
+
}
|
|
5079
|
+
let res;
|
|
5080
|
+
try {
|
|
5081
|
+
res = await (o.fetch ?? fetch)(`${server}/v1/me`, { headers: headers2(resolved.token), redirect: "error" });
|
|
5082
|
+
} catch (e) {
|
|
5083
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: {message}", { message: e instanceof Error ? e.message : String(e) }));
|
|
5084
|
+
return 1;
|
|
5085
|
+
}
|
|
5086
|
+
if (res.status === 401 || res.status === 403) {
|
|
5087
|
+
out2(authHint(resolved.source, res.status));
|
|
5088
|
+
return 1;
|
|
5089
|
+
}
|
|
5090
|
+
if (!res.ok) {
|
|
5091
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54: {status}", { status: res.status }));
|
|
5092
|
+
return 1;
|
|
5093
|
+
}
|
|
5094
|
+
const me = await res.json();
|
|
5095
|
+
out2(t("\u5229\u7528\u8005: {login} ({account})", { login: me.login ?? "-", account: me.account ?? "-" }));
|
|
5096
|
+
out2(t("\u30C8\u30FC\u30AF\u30F3\u306E\u51FA\u3069\u3053\u308D: {source}", { source: resolved.source === "env" ? t("\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN") : t("\u4FDD\u5B58\u6E08\u307F\u306E\u30B5\u30A4\u30F3\u30A4\u30F3") }));
|
|
5097
|
+
if (me.usage) {
|
|
5098
|
+
const mb = (n) => (n / 1e6).toFixed(1) + " MB";
|
|
5099
|
+
out2(t("\u8A08\u753B: {count} \u500B{limit}", { count: me.usage.projects, limit: me.limits ? ` / ${me.limits.projects}` : "" }));
|
|
5100
|
+
out2(t("\u4FDD\u5B58\u91CF: {used}{limit}", { used: mb(me.usage.contentBytes), limit: me.limits ? ` / ${mb(me.limits.contentBytes)}` : "" }));
|
|
5101
|
+
}
|
|
5102
|
+
for (const d of me.devices ?? []) out2(` - ${d.name}${d.current ? " " + t("(\u3053\u306E\u7AEF\u672B)") : ""}`);
|
|
5103
|
+
return 0;
|
|
5104
|
+
}
|
|
5105
|
+
function authHint(source, status) {
|
|
5106
|
+
if (status === 403) return t("\u3053\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u306F\u3001\u540C\u671F\u3092\u4F7F\u3048\u307E\u305B\u3093 (\u6B62\u3081\u3089\u308C\u3066\u3044\u308B\u3001\u307E\u305F\u306F\u62DB\u5F85\u3055\u308C\u3066\u3044\u307E\u305B\u3093)");
|
|
5107
|
+
if (source === "env") return t("\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN \u306E\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u52B9\u3067\u3059\u3002\u76F4\u3059\u304B\u3001\u5916\u3057\u3066\u304F\u3060\u3055\u3044 (\u5916\u3059\u3068\u3001\u4FDD\u5B58\u6E08\u307F\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u3092\u4F7F\u3044\u307E\u3059)");
|
|
5108
|
+
if (source === "file") return t("\u4FDD\u5B58\u6E08\u307F\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304C\u7121\u52B9\u306B\u306A\u3063\u3066\u3044\u307E\u3059 (\u671F\u9650\u5207\u308C\u30FB\u53D6\u308A\u6D88\u3057)\u3002boxglow login \u3092\u3084\u308A\u76F4\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
5109
|
+
return t("\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u3044\u307E\u305B\u3093\u3002boxglow login --server <URL> \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044 (\u307E\u305F\u306F\u3001\u74B0\u5883\u5909\u6570 BOXGLOW_TOKEN)");
|
|
5110
|
+
}
|
|
5111
|
+
|
|
5112
|
+
// cli/sync/watch.ts
|
|
5113
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
5114
|
+
import { join as join6 } from "node:path";
|
|
5115
|
+
var DEBOUNCE_MS = 2e3;
|
|
5116
|
+
var MAX_WAIT_MS = 1e4;
|
|
5117
|
+
var POLL_MS = 3e4;
|
|
5118
|
+
var IDLE_POLL_MS = 6e4;
|
|
5119
|
+
var IDLE_AFTER = 10;
|
|
5120
|
+
var RETRY_MS = 5e3;
|
|
5121
|
+
var RETRY_MAX_MS = 6e4;
|
|
5122
|
+
function signature(file) {
|
|
5123
|
+
try {
|
|
5124
|
+
const st = statSync4(file);
|
|
5125
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
5126
|
+
} catch {
|
|
5127
|
+
return "none";
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
function contentHash(file) {
|
|
5131
|
+
try {
|
|
5132
|
+
return hashOf(readFileSync7(file, "utf8"));
|
|
5133
|
+
} catch {
|
|
5134
|
+
return null;
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
5137
|
+
function bindingsFor(server) {
|
|
5138
|
+
const root = join6(configDir(), "sync");
|
|
5139
|
+
let names = [];
|
|
5140
|
+
try {
|
|
5141
|
+
names = readdirSync3(root);
|
|
5142
|
+
} catch (e) {
|
|
5143
|
+
if (e.code !== "ENOENT") throw e;
|
|
5144
|
+
}
|
|
5145
|
+
const states = [], unreadable = [];
|
|
5146
|
+
for (const name of names) {
|
|
5147
|
+
const dir2 = join6(root, name);
|
|
5148
|
+
if (!existsSync7(join6(dir2, "state.json"))) continue;
|
|
5149
|
+
try {
|
|
5150
|
+
const state = new StateStore(dir2).read();
|
|
5151
|
+
if (state && state.binding.server === normalizeServer(server)) states.push({ dir: dir2, state });
|
|
5152
|
+
} catch {
|
|
5153
|
+
unreadable.push(dir2);
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
return { states, unreadable };
|
|
5157
|
+
}
|
|
5158
|
+
function lockWatch(server) {
|
|
5159
|
+
const root = join6(configDir(), "sync");
|
|
5160
|
+
mkdirSync5(root, { recursive: true, mode: 448 });
|
|
5161
|
+
const path = join6(root, `watch-${hashOf(normalizeServer(server)).slice(0, 16)}`);
|
|
5162
|
+
try {
|
|
5163
|
+
return { unlock: lockFile(path, { waitMs: 0 }), path };
|
|
5164
|
+
} catch (e) {
|
|
5165
|
+
if (e instanceof FileBusy) return { unlock: null, path };
|
|
5166
|
+
throw e;
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
function haltKey(result) {
|
|
5170
|
+
const h = result.halt;
|
|
5171
|
+
return JSON.stringify([h.reason, h.token ?? null, h.approval ?? null, result.recovery?.token ?? null]);
|
|
5172
|
+
}
|
|
5173
|
+
var SyncWatcher = class {
|
|
5174
|
+
constructor(options) {
|
|
5175
|
+
this.options = options;
|
|
5176
|
+
this.wall = options.now ?? (() => Date.now());
|
|
5177
|
+
this.elapsed = options.elapsed ?? (() => performance.now());
|
|
5178
|
+
this.random = options.random ?? Math.random;
|
|
5179
|
+
this.nextPollAt = this.elapsed();
|
|
5180
|
+
}
|
|
5181
|
+
options;
|
|
5182
|
+
wall;
|
|
5183
|
+
elapsed;
|
|
5184
|
+
random;
|
|
5185
|
+
watched = /* @__PURE__ */ new Map();
|
|
5186
|
+
/** この tick で読んだ、結び付けの状態 (ファイル → 状態)。同期が要るかの目安にだけ使う (実際の同期は、ロックの中で読み直す) */
|
|
5187
|
+
states = /* @__PURE__ */ new Map();
|
|
5188
|
+
/** 次にサーバーを確かめる時点 */
|
|
5189
|
+
nextPollAt = 0;
|
|
5190
|
+
/** 変化が無かった確認の回数 (間隔を延ばす判断に使う) */
|
|
5191
|
+
quietPolls = 0;
|
|
5192
|
+
/** 通信の失敗が続いている間の、やり直しの間隔と、次に通信してよい時点 */
|
|
5193
|
+
retryMs = 0;
|
|
5194
|
+
blockedUntil = 0;
|
|
5195
|
+
/** 最後に確かめた、サーバーの履歴の世代 */
|
|
5196
|
+
epoch = null;
|
|
5197
|
+
/** 1 回分の処理が動いている間は、次の tick を重ねない */
|
|
5198
|
+
running = false;
|
|
5199
|
+
/** 見張っている計画のファイルの一覧 (試験の確認用) */
|
|
5200
|
+
files() {
|
|
5201
|
+
return [...this.watched.keys()];
|
|
5202
|
+
}
|
|
5203
|
+
/**
|
|
5204
|
+
* その時点で行うべきことを 1 回分行う: 結び付けの一覧を取り直す → 手元の変更を調べる → (時点が来ていれば) サーバーを確かめる → 送る・受け取る
|
|
5205
|
+
* Output: なし (出来事は onEvent で知らせる)。例外は投げない (想定外の失敗も出来事として知らせ、次の tick でやり直す)
|
|
5206
|
+
*/
|
|
5207
|
+
async tick() {
|
|
5208
|
+
if (this.running) return;
|
|
5209
|
+
this.running = true;
|
|
5210
|
+
try {
|
|
5211
|
+
await this.step();
|
|
5212
|
+
} catch (e) {
|
|
5213
|
+
this.options.onEvent?.({ kind: "error", file: "", message: e instanceof Error ? e.message : String(e) });
|
|
5214
|
+
} finally {
|
|
5215
|
+
this.running = false;
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
async step() {
|
|
5219
|
+
const at = this.elapsed();
|
|
5220
|
+
this.refreshBindings();
|
|
5221
|
+
for (const w of this.watched.values()) {
|
|
5222
|
+
const sig = signature(w.binding.file);
|
|
5223
|
+
if (sig !== w.seen) {
|
|
5224
|
+
w.seen = sig;
|
|
5225
|
+
this.markChanged(w, at);
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
if (at < this.blockedUntil) return;
|
|
5229
|
+
if (at >= this.nextPollAt && this.watched.size > 0) {
|
|
5230
|
+
try {
|
|
5231
|
+
const { epoch, heads } = await fetchHeads({ server: this.options.server, token: this.options.token, fetch: this.options.fetch });
|
|
5232
|
+
let changed = false;
|
|
5233
|
+
const epochChanged = this.epoch !== null && this.epoch !== epoch;
|
|
5234
|
+
this.epoch = epoch;
|
|
5235
|
+
for (const w of this.watched.values()) {
|
|
5236
|
+
if (w.broken) continue;
|
|
5237
|
+
const entry = heads.get(w.binding.remoteId);
|
|
5238
|
+
const head = entry?.deleted ? "(deleted)" : entry?.revision ?? null;
|
|
5239
|
+
const base = this.states.get(w.binding.file)?.base?.revision ?? null;
|
|
5240
|
+
w.head = head;
|
|
5241
|
+
if (epochChanged || head !== base && !(w.halted && w.halted.head === head)) {
|
|
5242
|
+
w.remoteAhead = true;
|
|
5243
|
+
changed = true;
|
|
5244
|
+
}
|
|
5245
|
+
if (w.checkedHash !== void 0 && contentHash(w.binding.file) !== w.checkedHash && w.firstChangeAt === null) {
|
|
5246
|
+
this.markChanged(w, at);
|
|
5247
|
+
changed = true;
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
this.retryMs = 0;
|
|
5251
|
+
this.authNotified = false;
|
|
5252
|
+
this.quietPolls = changed ? 0 : this.quietPolls + 1;
|
|
5253
|
+
const interval = this.quietPolls >= IDLE_AFTER ? IDLE_POLL_MS : POLL_MS;
|
|
5254
|
+
this.nextPollAt = at + interval * (0.9 + 0.2 * this.random());
|
|
5255
|
+
} catch (e) {
|
|
5256
|
+
if (!(e instanceof SyncNetworkError)) throw e;
|
|
5257
|
+
this.fail(at, e);
|
|
5258
|
+
return;
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
for (const w of this.watched.values()) {
|
|
5262
|
+
if (w.broken) continue;
|
|
5263
|
+
const localDue = w.firstChangeAt !== null && w.lastChangeAt !== null && (at - w.lastChangeAt >= DEBOUNCE_MS || at - w.firstChangeAt >= MAX_WAIT_MS);
|
|
5264
|
+
if (!localDue && !w.remoteAhead) continue;
|
|
5265
|
+
if (w.halted && !w.remoteAhead && contentHash(w.binding.file) === w.halted.hash) {
|
|
5266
|
+
w.firstChangeAt = w.lastChangeAt = null;
|
|
5267
|
+
continue;
|
|
5268
|
+
}
|
|
5269
|
+
const ok = await this.syncOne(w, at);
|
|
5270
|
+
if (!ok) return;
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
/** 「手元が変わった」と記録する (まとめる変更の最初と最後の時点) */
|
|
5274
|
+
markChanged(w, at) {
|
|
5275
|
+
w.firstChangeAt ??= at;
|
|
5276
|
+
w.lastChangeAt = at;
|
|
5277
|
+
}
|
|
5278
|
+
/** 結び付けの一覧を取り直す (常時の同期を始めた後で結び付けた計画も、次の tick から受け持つ) */
|
|
5279
|
+
refreshBindings() {
|
|
5280
|
+
const { states, unreadable } = bindingsFor(this.options.server);
|
|
5281
|
+
this.states = new Map(states.map((s) => [s.state.binding.file, s.state]));
|
|
5282
|
+
const broken = new Set(unreadable);
|
|
5283
|
+
for (const [file, w] of [...this.watched]) {
|
|
5284
|
+
const dir2 = bindingDir(file, this.options.server);
|
|
5285
|
+
if (this.states.has(file)) {
|
|
5286
|
+
if (w.broken) {
|
|
5287
|
+
w.broken = false;
|
|
5288
|
+
w.remoteAhead = true;
|
|
5289
|
+
w.halted = null;
|
|
5290
|
+
w.notified = null;
|
|
5291
|
+
}
|
|
5292
|
+
continue;
|
|
5293
|
+
}
|
|
5294
|
+
if (broken.has(dir2)) {
|
|
5295
|
+
if (!w.broken) {
|
|
5296
|
+
w.broken = true;
|
|
5297
|
+
this.options.onEvent?.({ kind: "error", file, message: "sync state is unreadable: " + dir2 });
|
|
5298
|
+
}
|
|
5299
|
+
continue;
|
|
5300
|
+
}
|
|
5301
|
+
this.watched.delete(file);
|
|
5302
|
+
}
|
|
5303
|
+
for (const [file, state] of this.states) {
|
|
5304
|
+
if (!this.watched.has(file)) {
|
|
5305
|
+
this.watched.set(file, {
|
|
5306
|
+
binding: state.binding,
|
|
5307
|
+
seen: signature(file),
|
|
5308
|
+
checkedHash: void 0,
|
|
5309
|
+
firstChangeAt: null,
|
|
5310
|
+
lastChangeAt: null,
|
|
5311
|
+
remoteAhead: true,
|
|
5312
|
+
head: void 0,
|
|
5313
|
+
halted: null,
|
|
5314
|
+
notified: null,
|
|
5315
|
+
broken: false
|
|
5316
|
+
});
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
/** 「利用者を確かめられない」を知らせ済みか (同じことを繰り返し知らせない) */
|
|
5321
|
+
authNotified = false;
|
|
5322
|
+
/** 通信の失敗: やり直しの間隔を延ばす (最初 5 秒、倍々で 60 秒まで)。利用者を確かめられない場合は、1 回だけ知らせて、長い間隔で待つ */
|
|
5323
|
+
fail(at, e) {
|
|
5324
|
+
if (e instanceof SyncAuthError) {
|
|
5325
|
+
this.retryMs = RETRY_MAX_MS;
|
|
5326
|
+
this.blockedUntil = at + this.retryMs;
|
|
5327
|
+
if (!this.authNotified) {
|
|
5328
|
+
this.authNotified = true;
|
|
5329
|
+
this.options.onEvent?.({ kind: "auth" });
|
|
5330
|
+
}
|
|
5331
|
+
return;
|
|
5332
|
+
}
|
|
5333
|
+
this.backOff(at, e.message);
|
|
5334
|
+
}
|
|
5335
|
+
/** 通信の失敗: やり直しの間隔を延ばす (最初 5 秒、倍々で 60 秒まで) */
|
|
5336
|
+
backOff(at, message) {
|
|
5337
|
+
this.retryMs = this.retryMs === 0 ? RETRY_MS : Math.min(this.retryMs * 2, RETRY_MAX_MS);
|
|
5338
|
+
this.blockedUntil = at + this.retryMs;
|
|
5339
|
+
this.options.onEvent?.({ kind: "network", message, retryInMs: this.retryMs });
|
|
5340
|
+
}
|
|
5341
|
+
/**
|
|
5342
|
+
* 計画 1 つを同期する
|
|
5343
|
+
* Output: 通信できたら true (止まった・ほかが動いていた場合も true)。通信できなかったら false
|
|
5344
|
+
*/
|
|
5345
|
+
async syncOne(w, at) {
|
|
5346
|
+
const file = w.binding.file;
|
|
5347
|
+
let result;
|
|
5348
|
+
try {
|
|
5349
|
+
result = await syncOnce({ file, server: this.options.server, token: this.options.token, fetch: this.options.fetch, now: () => new Date(this.wall()) });
|
|
5350
|
+
} catch (e) {
|
|
5351
|
+
if (e instanceof SyncNetworkError) {
|
|
5352
|
+
this.fail(at, e);
|
|
5353
|
+
return false;
|
|
5354
|
+
}
|
|
5355
|
+
w.halted = { hash: contentHash(file), head: w.head };
|
|
5356
|
+
w.remoteAhead = false;
|
|
5357
|
+
w.firstChangeAt = w.lastChangeAt = null;
|
|
5358
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
5359
|
+
if (w.notified !== message) {
|
|
5360
|
+
w.notified = message;
|
|
5361
|
+
this.options.onEvent?.({ kind: "error", file, message });
|
|
5362
|
+
}
|
|
5363
|
+
return true;
|
|
5364
|
+
}
|
|
5365
|
+
if (result.status === "busy") return true;
|
|
5366
|
+
try {
|
|
5367
|
+
const fresh = new StateStore(bindingDir(file, this.options.server)).read();
|
|
5368
|
+
if (fresh) this.states.set(file, fresh);
|
|
5369
|
+
} catch {
|
|
5370
|
+
}
|
|
5371
|
+
w.seen = signature(file);
|
|
5372
|
+
w.checkedHash = result.localHash;
|
|
5373
|
+
w.firstChangeAt = w.lastChangeAt = null;
|
|
5374
|
+
w.remoteAhead = false;
|
|
5375
|
+
if (result.localHash !== void 0 && contentHash(file) !== result.localHash) this.markChanged(w, at);
|
|
5376
|
+
if (result.status === "synced") {
|
|
5377
|
+
const resumed = w.halted !== null;
|
|
5378
|
+
w.halted = null;
|
|
5379
|
+
w.notified = null;
|
|
5380
|
+
if (result.pulled || result.pushed || resumed) this.options.onEvent?.({ kind: "synced", file, pulled: result.pulled, pushed: result.pushed, revision: result.revision });
|
|
5381
|
+
return true;
|
|
5382
|
+
}
|
|
5383
|
+
w.halted = { hash: result.localHash, head: w.head };
|
|
5384
|
+
const key = haltKey(result);
|
|
5385
|
+
if (w.notified !== key) {
|
|
5386
|
+
w.notified = key;
|
|
5387
|
+
this.options.onEvent?.({ kind: "halted", file, result });
|
|
5388
|
+
}
|
|
5389
|
+
return true;
|
|
5390
|
+
}
|
|
5391
|
+
};
|
|
5392
|
+
|
|
5393
|
+
// cli/sync/command.ts
|
|
5394
|
+
async function runSyncCommand(o, out2) {
|
|
5395
|
+
let server = o.server ?? process.env.BOXGLOW_SERVER;
|
|
5396
|
+
if (!server) {
|
|
5397
|
+
const { bindings: bound, unreadable } = bindingsOf(o.file);
|
|
5398
|
+
if (bound.length === 1) server = bound[0].server;
|
|
5399
|
+
else if (bound.length === 0) {
|
|
5400
|
+
out2(t("\u540C\u671F\u5148\u304C\u6C7A\u307E\u3063\u3066\u3044\u307E\u305B\u3093\u3002\u521D\u3081\u3066\u7D50\u3073\u4ED8\u3051\u308B\u3068\u304D\u306F boxglow sync --server <URL> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5401
|
+
if (unreadable.length > 0) out2(t("\u540C\u671F\u306E\u72B6\u614B\u3092\u8AAD\u3081\u306A\u3044\u30D5\u30A9\u30EB\u30C0\u304C\u3042\u308A\u307E\u3059 (\u3053\u306E\u8A08\u753B\u306E\u7D50\u3073\u4ED8\u3051\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u6D88\u3055\u305A\u306B\u3001\u4E2D\u8EAB\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044): {list}", { list: unreadable.join(", ") }));
|
|
5402
|
+
return 1;
|
|
5403
|
+
} else {
|
|
5404
|
+
out2(t("\u3053\u306E\u8A08\u753B\u306F\u8907\u6570\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u307E\u3059\u3002--server <URL> \u3067\u9078\u3093\u3067\u304F\u3060\u3055\u3044: {list}", { list: bound.map((b) => b.server).join(", ") }));
|
|
5405
|
+
return 1;
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
if (!server) return 1;
|
|
5409
|
+
if (o.recover && o.applied === o.notApplied) {
|
|
5410
|
+
out2(t("--recover \u306B\u306F --applied (\u53CD\u6620\u6E08\u307F\u3068\u3057\u3066\u7D9A\u3051\u308B) \u304B --not-applied (\u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u3082\u306E\u3068\u3057\u3066\u7D9A\u3051\u308B) \u306E\u3069\u3061\u3089\u304B\u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044"));
|
|
5411
|
+
return 1;
|
|
5412
|
+
}
|
|
5413
|
+
if ((o.resolve || o.link) && o.prefer !== "local" && o.prefer !== "remote") {
|
|
5414
|
+
out2(t("--resolve / --link \u306B\u306F --prefer local (\u624B\u5143\u3092\u63A1\u308B) \u304B --prefer remote (\u30B5\u30FC\u30D0\u30FC\u3092\u63A1\u308B) \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044"));
|
|
5415
|
+
return 1;
|
|
5416
|
+
}
|
|
5417
|
+
const prefer = o.prefer;
|
|
5418
|
+
let result;
|
|
5419
|
+
try {
|
|
5420
|
+
result = await syncOnce({
|
|
5421
|
+
file: o.file,
|
|
5422
|
+
server,
|
|
5423
|
+
remoteId: o.project,
|
|
5424
|
+
token: resolveToken(server)?.token,
|
|
5425
|
+
approvedDeletion: o.adopt,
|
|
5426
|
+
restoreDeletion: o.restore,
|
|
5427
|
+
resolution: o.resolve ? { token: o.resolve, prefer } : void 0,
|
|
5428
|
+
firstLink: o.link ? { token: o.link, prefer } : void 0,
|
|
5429
|
+
recover: o.recover ? { token: o.recover, applied: !!o.applied } : void 0,
|
|
5430
|
+
confirmAccount: o.account
|
|
5431
|
+
});
|
|
5432
|
+
} catch (e) {
|
|
5433
|
+
if (e instanceof SyncRejectedError) {
|
|
5434
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u304C\u3001\u3053\u306E\u8A08\u753B\u306E\u540C\u671F\u3092\u53D7\u3051\u4ED8\u3051\u307E\u305B\u3093\u3067\u3057\u305F (\u5F85\u3063\u3066\u3082\u76F4\u308A\u307E\u305B\u3093)\u3002\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u306F\u6B8B\u3057\u3066\u3042\u308A\u307E\u3059: {status} {detail}", { status: e.status, detail: rejectionHint(e.status) }));
|
|
5435
|
+
out2(t("\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u76F4\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6 boxglow sync \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u65AD\u3089\u308C\u305F\u9001\u4FE1\u306F\u9001\u308A\u76F4\u3055\u305A\u3001\u30B5\u30FC\u30D0\u30FC\u3067\u53D7\u7406\u6E08\u307F\u304B\u30FB\u53D6\u308A\u6D88\u3059\u304B\u3092\u78BA\u5B9A\u3055\u305B\u3066\u304B\u3089\u3001\u4ECA\u306E\u624B\u5143\u306E\u5185\u5BB9\u3067\u9001\u308A\u76F4\u3057\u307E\u3059"));
|
|
5436
|
+
return 2;
|
|
5437
|
+
}
|
|
5438
|
+
if (e instanceof SyncAuthError) {
|
|
5439
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u304C\u5229\u7528\u8005\u3092\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u306F\u6B8B\u3057\u3066\u3042\u308A\u307E\u3059"));
|
|
5440
|
+
out2(authHint(resolveToken(server)?.source ?? null, e.status));
|
|
5441
|
+
return 1;
|
|
5442
|
+
}
|
|
5443
|
+
if (e instanceof SyncNetworkError) {
|
|
5444
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F (\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u306F\u6B8B\u3057\u3066\u3042\u308A\u307E\u3059\u3002\u3082\u3046\u4E00\u5EA6 boxglow sync \u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u7D9A\u304D\u304B\u3089\u9032\u307F\u307E\u3059): {message}", { message: e.message }));
|
|
5445
|
+
return 1;
|
|
5446
|
+
}
|
|
5447
|
+
if (e instanceof SyncStateUnreadable) {
|
|
5448
|
+
out2(t("\u540C\u671F\u306E\u72B6\u614B\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u3081\u307E\u305B\u3093\u3002\u81EA\u52D5\u3067\u306F\u76F4\u3057\u307E\u305B\u3093 (\u6D88\u3059\u3068\u3001\u3084\u308A\u304B\u3051\u306E\u64CD\u4F5C\u3068\u524D\u56DE\u305D\u308D\u3048\u305F\u4E2D\u8EAB\u306E\u8A18\u9332\u3092\u5931\u3044\u307E\u3059): {path} ({problem})", { path: e.path, problem: e.problem }));
|
|
5449
|
+
return 1;
|
|
5450
|
+
}
|
|
5451
|
+
throw e;
|
|
5452
|
+
}
|
|
5453
|
+
if (result.status === "synced") {
|
|
5454
|
+
if (result.edited > 0) out2(t("\u6D88\u3048\u3066\u3044\u305F\u8A2D\u5B9A\u3092\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u307E\u3057\u305F\u3002"));
|
|
5455
|
+
out2(result.pulled || result.pushed ? t("\u540C\u671F\u3057\u307E\u3057\u305F (\u53D7\u3051\u53D6\u308A {pulled} \u56DE\u3001\u9001\u308A {pushed} \u56DE)\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u7248: {revision}", { pulled: result.pulled, pushed: result.pushed, revision: result.revision ?? "-" }) : t("\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u7248: {revision}", { revision: result.revision ?? "-" }));
|
|
5456
|
+
return 0;
|
|
5457
|
+
}
|
|
5458
|
+
if (result.status === "busy") {
|
|
5459
|
+
out2(result.what === "file" ? t("\u8A08\u753B\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u66F8\u304D\u8FBC\u307F\u4E2D\u306E\u305F\u3081\u3001\u4ECA\u56DE\u306F\u9032\u3081\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u5C11\u3057\u5F85\u3063\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044") : t("\u3053\u306E\u8A08\u753B\u306E\u540C\u671F\u304C\u3001\u307B\u304B\u3067\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u7D42\u308F\u3063\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5460
|
+
return 0;
|
|
5461
|
+
}
|
|
5462
|
+
describeHalt(result, o.file, out2);
|
|
5463
|
+
return 2;
|
|
5464
|
+
}
|
|
5465
|
+
function describeHalt(result, file, out2) {
|
|
5466
|
+
const o = { file };
|
|
5467
|
+
const halt2 = result.halt;
|
|
5468
|
+
const target = ` --file ${quote(result.target.file)}`;
|
|
5469
|
+
const fullTarget = ` --server ${quote(result.target.server)} --project ${quote(result.target.remoteId)}${target}`;
|
|
5470
|
+
out2(t("\u540C\u671F\u3092\u6B62\u3081\u307E\u3057\u305F\u3002"));
|
|
5471
|
+
if (result.pulled > 0) out2(t("\u6B62\u307E\u308B\u524D\u306B\u3001\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4\u3092\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u66F8\u304D\u307E\u3057\u305F ({count} \u56DE)\u3002", { count: result.pulled }));
|
|
5472
|
+
if (result.pushed > 0) out2(t("\u6B62\u307E\u308B\u524D\u306B\u3001\u624B\u5143\u306E\u5909\u66F4\u3092\u30B5\u30FC\u30D0\u30FC\u3078\u9001\u308A\u307E\u3057\u305F ({count} \u56DE)\u3002", { count: result.pushed }));
|
|
5473
|
+
if (result.edited > 0) out2(t("\u6B62\u307E\u308B\u524D\u306B\u3001\u6D88\u3048\u3066\u3044\u305F\u8A2D\u5B9A\u3092\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u307E\u3057\u305F\u3002"));
|
|
5474
|
+
if (result.pulled === 0 && result.pushed === 0 && result.edited === 0) out2(t("\u3053\u306E\u5B9F\u884C\u3067\u306F\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3082\u30B5\u30FC\u30D0\u30FC\u3082\u5909\u3048\u3066\u3044\u307E\u305B\u3093\u3002"));
|
|
5475
|
+
switch (halt2.reason) {
|
|
5476
|
+
case "conflicts":
|
|
5477
|
+
out2(t("\u624B\u5143\u3068\u30B5\u30FC\u30D0\u30FC\u3067\u3001\u540C\u3058\u9805\u76EE\u304C\u5225\u306E\u5024\u306B\u5909\u308F\u3063\u3066\u3044\u307E\u3059 ({count} \u4EF6):", { count: halt2.conflicts.length }));
|
|
5478
|
+
for (const c of halt2.conflicts) out2(` - ${c.path}: ${t("\u624B\u5143")} ${JSON.stringify(c.ours)} / ${t("\u30B5\u30FC\u30D0\u30FC")} ${JSON.stringify(c.theirs)}`);
|
|
5479
|
+
out2(t("\u8868\u793A\u3057\u305F\u9805\u76EE\u3092\u3001\u624B\u5143\u306E\u5024\u306B\u6C7A\u3081\u308B\u306A\u3089 (\u8868\u793A\u3057\u3066\u3044\u306A\u3044\u9805\u76EE\u306F\u3001\u4ECA\u307E\u3067\u3069\u304A\u308A\u81EA\u52D5\u3067\u5408\u308F\u305B\u307E\u3059):"));
|
|
5480
|
+
out2(` boxglow sync --resolve ${halt2.token} --prefer local${target}`);
|
|
5481
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u306E\u5024\u306B\u6C7A\u3081\u308B\u306A\u3089:"));
|
|
5482
|
+
out2(` boxglow sync --resolve ${halt2.token} --prefer remote${target}`);
|
|
5483
|
+
out2(t("\u9805\u76EE\u3054\u3068\u306B\u9078\u3073\u5206\u3051\u305F\u3044\u3068\u304D\u306F\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3067\u63A1\u308A\u305F\u3044\u5024\u306B\u76F4\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6 boxglow sync \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044 (\u30B5\u30FC\u30D0\u30FC\u3068\u540C\u3058\u5024\u306B\u3057\u305F\u9805\u76EE\u306F\u3001\u7AF6\u5408\u306B\u306A\u308A\u307E\u305B\u3093)"));
|
|
5484
|
+
break;
|
|
5485
|
+
case "invalid-merge":
|
|
5486
|
+
out2(t("\u624B\u5143\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4\u3092\u5408\u308F\u305B\u308B\u3068\u3001\u8A08\u753B\u3068\u3057\u3066\u6B63\u3057\u304F\u306A\u3044\u5F62\u306B\u306A\u308A\u307E\u3059: {problem}", { problem: halt2.problem }));
|
|
5487
|
+
out2(t("\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3067\u3001\u3069\u3061\u3089\u304B\u306E\u5909\u66F4\u3092\u53D6\u308A\u6D88\u3057\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5488
|
+
break;
|
|
5489
|
+
case "invalid-local":
|
|
5490
|
+
out2(t("\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3001\u8A08\u753B\u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093: {problem}", { problem: halt2.problem }));
|
|
5491
|
+
break;
|
|
5492
|
+
case "invalid-remote":
|
|
5493
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u306E\u4E2D\u8EAB\u304C\u3001\u8A08\u753B\u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093: {problem}", { problem: halt2.problem }));
|
|
5494
|
+
break;
|
|
5495
|
+
case "protected-deletion":
|
|
5496
|
+
out2(t("\u524D\u56DE\u305D\u308D\u3048\u305F\u3068\u304D\u306B\u5728\u3063\u305F\u8A2D\u5B9A\u304C\u3001\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u6D88\u3048\u3066\u3044\u307E\u3059: {keys}", { keys: halt2.keys.join(", ") }));
|
|
5497
|
+
out2(t("\u53E4\u3044\u7248\u306E Boxglow \u304C\u4FDD\u5B58\u306E\u3068\u304D\u306B\u843D\u3068\u3057\u305F\u306E\u304B\u3082\u3057\u308C\u307E\u305B\u3093 (\u66F8\u304D\u624B\u306F\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093)\u3002\u610F\u56F3\u3057\u305F\u524A\u9664\u306A\u3089\u3001\u6B21\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u305D\u306E\u524A\u9664\u3092\u9001\u308A\u307E\u3059:"));
|
|
5498
|
+
out2(` boxglow sync --adopt ${halt2.approval}${target}`);
|
|
5499
|
+
out2(t("\u610F\u56F3\u3057\u3066\u3044\u306A\u3044\u306A\u3089\u3001\u6B21\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u6D88\u3048\u305F\u8A2D\u5B9A\u3060\u3051\u3092\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u3066\u3001\u540C\u671F\u3092\u7D9A\u3051\u307E\u3059 (\u307B\u304B\u306E\u7DE8\u96C6\u306F\u305D\u306E\u307E\u307E\u3067\u3059):"));
|
|
5500
|
+
out2(` boxglow sync --restore ${halt2.approval}${target}`);
|
|
5501
|
+
break;
|
|
5502
|
+
case "recover-pull": {
|
|
5503
|
+
out2(t("\u524D\u56DE\u306E\u540C\u671F\u304C\u3001\u30B5\u30FC\u30D0\u30FC\u306E\u5909\u66F4 (\u7248 {revision}) \u3092\u624B\u5143\u306B\u66F8\u304F\u9014\u4E2D\u3067\u7D42\u308F\u3063\u3066\u3044\u307E\u3057\u305F\u3002\u66F8\u304D\u8FBC\u3081\u305F\u304B\u3069\u3046\u304B\u3092\u3001\u4ECA\u306E\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u306F\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3002", { revision: halt2.pending.remote.revision }));
|
|
5504
|
+
out2(t("\u7D9A\u3051\u65B9\u306F 2 \u3064\u3042\u308A\u307E\u3059\u3002\u305D\u308C\u305E\u308C\u3092\u9078\u3093\u3060\u5834\u5408\u306B\u8D77\u304D\u308B\u3053\u3068\u3092\u898B\u6BD4\u3079\u3066\u3001\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002"));
|
|
5505
|
+
const recovery = result.recovery;
|
|
5506
|
+
if (recovery) {
|
|
5507
|
+
const show = (label, flag, outcome) => {
|
|
5508
|
+
out2("");
|
|
5509
|
+
out2(`${label}: boxglow sync --recover ${recovery.token} ${flag}${target}`);
|
|
5510
|
+
out2(" " + t("\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u5165\u308B\u5909\u66F4:") + (outcome.localChanges.length ? "" : " " + t("\u306A\u3057")));
|
|
5511
|
+
for (const line of outcome.localChanges) out2(" - " + line);
|
|
5512
|
+
if (outcome.next === "push") {
|
|
5513
|
+
out2(" " + t("\u305D\u306E\u3042\u3068\u3001\u30B5\u30FC\u30D0\u30FC\u3078\u9001\u308B\u5909\u66F4:") + (outcome.remoteChanges.length ? "" : " " + t("\u8A18\u9332 (\u30ED\u30B0\u306A\u3069) \u3060\u3051")));
|
|
5514
|
+
for (const line of outcome.remoteChanges) out2(" - " + line);
|
|
5515
|
+
} else if (outcome.next === "none") out2(" " + t("\u30B5\u30FC\u30D0\u30FC\u3078\u306F\u4F55\u3082\u9001\u308A\u307E\u305B\u3093"));
|
|
5516
|
+
else out2(" " + t("\u30B5\u30FC\u30D0\u30FC\u3078\u9001\u308B\u524D\u306B\u3001\u5225\u306E\u78BA\u8A8D\u304C\u5FC5\u8981\u306B\u306A\u3063\u3066\u3001\u3082\u3046\u4E00\u5EA6\u6B62\u307E\u308A\u307E\u3059 ({reason})", { reason: outcome.next }));
|
|
5517
|
+
};
|
|
5518
|
+
show(t("A. \u53D7\u3051\u53D6\u308A\u306F\u624B\u5143\u306B\u53CD\u6620\u6E08\u307F\u3068\u3057\u3066\u7D9A\u3051\u308B"), "--applied", recovery.applied);
|
|
5519
|
+
show(t("B. \u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u3082\u306E\u3068\u3057\u3066\u7D9A\u3051\u308B"), "--not-applied", recovery.notApplied);
|
|
5520
|
+
out2("");
|
|
5521
|
+
}
|
|
5522
|
+
out2(t("\u3069\u3061\u3089\u3068\u3082\u6C7A\u3081\u3089\u308C\u306A\u3044\u3068\u304D\u306F\u3001\u4F55\u3082\u5B9F\u884C\u3057\u306A\u3051\u308C\u3070\u3001\u6B62\u307E\u3063\u305F\u307E\u307E\u3067\u3059 (\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3001\u305D\u306E\u307E\u307E\u7DE8\u96C6\u3067\u304D\u307E\u3059)\u3002\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u5225\u306E\u5834\u6240\u3078\u5199\u3057\u3066\u304B\u3089\u9078\u3076\u3053\u3068\u3082\u3067\u304D\u307E\u3059"));
|
|
5523
|
+
break;
|
|
5524
|
+
}
|
|
5525
|
+
case "history-changed":
|
|
5526
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u306E\u5C65\u6B74\u304C\u3001\u524D\u56DE\u305D\u308D\u3048\u305F\u3068\u304D\u304B\u3089\u5909\u308F\u3063\u3066\u3044\u307E\u3059 (\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u304B\u3089\u306E\u5FA9\u65E7\u306A\u3069)\u3002\u524D\u56DE\u306E\u7D9A\u304D\u3068\u3057\u3066\u306F\u540C\u671F\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D3\u30B9\u306E\u6848\u5185\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044"));
|
|
5527
|
+
break;
|
|
5528
|
+
case "stale-operation":
|
|
5529
|
+
out2(t("\u524D\u56DE\u306E\u9001\u4FE1\u306E\u8A18\u9332\u304C\u53E4\u3059\u304E\u308B\u3001\u307E\u305F\u306F\u65E5\u6642\u304C\u8AAD\u3081\u306A\u3044\u305F\u3081\u3001\u81EA\u52D5\u3067\u306F\u7D9A\u3051\u3089\u308C\u307E\u305B\u3093"));
|
|
5530
|
+
break;
|
|
5531
|
+
case "first-link":
|
|
5532
|
+
out2(t("\u624B\u5143\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u4E21\u65B9\u306B\u3001\u9055\u3046\u4E2D\u8EAB\u306E\u8A08\u753B\u304C\u3042\u308A\u307E\u3059\u3002\u5171\u901A\u306E\u5143\u304C\u5206\u304B\u3089\u306A\u3044\u306E\u3067\u3001\u81EA\u52D5\u3067\u306F\u5408\u308F\u305B\u307E\u305B\u3093\u3002\u3069\u3061\u3089\u3092\u63A1\u308B\u304B\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002"));
|
|
5533
|
+
out2(t("\u624B\u5143\u306E\u8A08\u753B\u3092\u63A1\u308B (\u30B5\u30FC\u30D0\u30FC\u306E\u8A08\u753B\u3092\u7F6E\u304D\u63DB\u3048\u308B\u3002\u524D\u306E\u4E2D\u8EAB\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u5C65\u6B74\u306B\u6B8B\u308A\u307E\u3059):"));
|
|
5534
|
+
out2(` boxglow sync --link ${halt2.token} --prefer local${fullTarget}`);
|
|
5535
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u306E\u8A08\u753B\u3092\u63A1\u308B (\u624B\u5143\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u7F6E\u304D\u63DB\u3048\u308B\u3002\u524D\u306E\u4E2D\u8EAB\u306F\u3001\u540C\u3058\u30D5\u30A9\u30EB\u30C0\u306B <\u30D5\u30A1\u30A4\u30EB\u540D>.before-sync-....json \u3068\u3057\u3066\u6B8B\u3057\u307E\u3059):"));
|
|
5536
|
+
out2(` boxglow sync --link ${halt2.token} --prefer remote${fullTarget}`);
|
|
5537
|
+
break;
|
|
5538
|
+
case "remote-deleted":
|
|
5539
|
+
out2(t("\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u3001\u3053\u306E\u8A08\u753B\u306F\u6D88\u3055\u308C\u3066\u3044\u307E\u3059 (\u81EA\u52D5\u3067\u306F\u4F5C\u308A\u76F4\u3057\u307E\u305B\u3093)"));
|
|
5540
|
+
break;
|
|
5541
|
+
case "local-missing":
|
|
5542
|
+
out2(t("\u624B\u5143\u306E\u8A08\u753B\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093: {file}", { file: o.file }));
|
|
5543
|
+
break;
|
|
5544
|
+
case "base-missing":
|
|
5545
|
+
out2(t("\u540C\u671F\u306E\u72B6\u614B\u306E\u30D5\u30A9\u30EB\u30C0\u304B\u3089\u3001\u524D\u56DE\u305D\u308D\u3048\u305F\u4E2D\u8EAB\u306E\u5199\u3057\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (\u307E\u305F\u306F\u58CA\u308C\u3066\u3044\u307E\u3059)\u3002\u81EA\u52D5\u3067\u306F\u7D9A\u3051\u3089\u308C\u307E\u305B\u3093\u3002\u72B6\u614B\u306E\u8A18\u9332\u306F\u305D\u306E\u307E\u307E\u6B8B\u3057\u3066\u6B62\u307E\u3063\u3066\u3044\u307E\u3059 (\u6D88\u3055\u306A\u3044\u3067\u304F\u3060\u3055\u3044)"));
|
|
5546
|
+
break;
|
|
5547
|
+
case "binding-target":
|
|
5548
|
+
out2(t("\u3053\u306E\u8A08\u753B\u306F\u3001\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u8A08\u753B {bound} \u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u307E\u3059\u3002\u6307\u5B9A\u3055\u308C\u305F {requested} \u3068\u306F\u9055\u3046\u306E\u3067\u3001\u4F55\u3082\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u7D50\u3073\u4ED8\u3051\u6E08\u307F\u306E\u8A08\u753B\u3068\u540C\u671F\u3059\u308B\u306A\u3089\u3001--project \u3092\u4ED8\u3051\u305A\u306B\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044", { bound: halt2.bound, requested: halt2.requested }));
|
|
5549
|
+
break;
|
|
5550
|
+
case "bound-elsewhere":
|
|
5551
|
+
out2(t("\u3053\u306E\u8A08\u753B\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3001\u3059\u3067\u306B\u5225\u306E\u30B5\u30FC\u30D0\u30FC\u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u307E\u3059: {server} (1 \u3064\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u30012 \u3064\u306E\u30B5\u30FC\u30D0\u30FC\u3078\u306F\u7D50\u3073\u4ED8\u3051\u307E\u305B\u3093)", { server: halt2.server }));
|
|
5552
|
+
break;
|
|
5553
|
+
case "unreadable-bindings":
|
|
5554
|
+
out2(t("\u540C\u671F\u306E\u72B6\u614B\u3092\u8AAD\u3081\u306A\u3044\u30D5\u30A9\u30EB\u30C0\u304C\u3042\u308B\u305F\u3081\u3001\u3053\u306E\u8A08\u753B\u304C\u3059\u3067\u306B\u7D50\u3073\u4ED8\u3044\u3066\u3044\u308B\u304B\u3092\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093\u3002\u65B0\u3057\u3044\u7D50\u3073\u4ED8\u3051\u306F\u4F5C\u308A\u307E\u305B\u3093 (\u30D5\u30A9\u30EB\u30C0\u306F\u6D88\u3055\u305A\u306B\u3001\u4E2D\u8EAB\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044): {list}", { list: halt2.dirs.join(", ") }));
|
|
5555
|
+
break;
|
|
5556
|
+
case "choice-not-applied":
|
|
5557
|
+
out2(halt2.choice === "firstLink" ? t("--link \u306E\u9078\u629E\u306F\u3001\u4ECA\u306E\u72B6\u614B\u306B\u306F\u5F53\u3066\u306F\u307E\u308A\u307E\u305B\u3093 (\u540C\u671F\u306E\u5BFE\u8C61\u304C\u3001\u9078\u629E\u3092\u8868\u793A\u3057\u305F\u3068\u304D\u3068\u9055\u3046\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059)\u3002\u4F55\u3082\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u8868\u793A\u3055\u308C\u305F\u30B3\u30DE\u30F3\u30C9\u3092\u3001--server\u30FB--project\u30FB--file \u3092\u4ED8\u3051\u305F\u307E\u307E\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044") : t("--resolve \u306E\u9078\u629E\u306F\u3001\u4ECA\u306E\u72B6\u614B\u306B\u306F\u5F53\u3066\u306F\u307E\u308A\u307E\u305B\u3093 (\u7AF6\u5408\u304C\u3082\u3046\u7121\u3044\u3001\u307E\u305F\u306F\u5BFE\u8C61\u304C\u9055\u3044\u307E\u3059)\u3002\u4F55\u3082\u3057\u3066\u3044\u307E\u305B\u3093\u3002boxglow sync \u3067\u3082\u3046\u4E00\u5EA6\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044"));
|
|
5558
|
+
break;
|
|
5559
|
+
case "account-mismatch":
|
|
5560
|
+
out2(t("\u3053\u306E\u8A08\u753B\u306E\u7D50\u3073\u4ED8\u3051\u306F\u3001\u5225\u306E\u5229\u7528\u8005 ({bound}) \u306E\u3082\u306E\u3067\u3059\u3002\u4ECA\u306E\u30C8\u30FC\u30AF\u30F3\u306E\u5229\u7528\u8005\u306F {actual} \u3067\u3059\u3002\u5225\u306E\u5229\u7528\u8005\u306E\u8A08\u753B\u3092\u7F6E\u304D\u63DB\u3048\u306A\u3044\u3088\u3046\u3001\u4F55\u3082\u9001\u3089\u305A\u3001\u4F55\u3082\u66F8\u3044\u3066\u3044\u307E\u305B\u3093\u3002\u7D50\u3073\u4ED8\u3051\u305F\u3068\u304D\u306E\u5229\u7528\u8005\u306E\u30C8\u30FC\u30AF\u30F3\u3067\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044", { bound: halt2.bound, actual: halt2.actual }));
|
|
5561
|
+
break;
|
|
5562
|
+
case "account-unconfirmed":
|
|
5563
|
+
out2(t("\u3053\u306E\u8A08\u753B\u306E\u7D50\u3073\u4ED8\u3051\u306B\u306F\u3001\u5229\u7528\u8005\u306E\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093 (\u8A18\u9332\u3059\u308B\u3088\u3046\u306B\u306A\u308B\u524D\u306B\u7D50\u3073\u4ED8\u3051\u305F\u3082\u306E\u3067\u3059)\u3002\u4ECA\u306E\u30C8\u30FC\u30AF\u30F3\u306E\u5229\u7528\u8005\u306F {account} \u3067\u3059\u3002\u3053\u306E\u5229\u7528\u8005\u3067\u7D50\u3073\u4ED8\u3051\u305F\u3082\u306E\u306A\u3089\u3001\u6B21\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u5229\u7528\u8005\u3092\u8A18\u9332\u3057\u3066\u540C\u671F\u3092\u7D9A\u3051\u307E\u3059:", { account: halt2.account }));
|
|
5564
|
+
out2(` boxglow sync --account ${quote(halt2.account)}${target}`);
|
|
5565
|
+
out2(t("\u5225\u306E\u5229\u7528\u8005\u3067\u7D50\u3073\u4ED8\u3051\u305F\u3082\u306E\u306A\u3089\u3001\u5B9F\u884C\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044 (\u305D\u306E\u3068\u304D\u306E\u30C8\u30FC\u30AF\u30F3\u306B\u76F4\u3057\u3066\u304B\u3089\u3001\u3082\u3046\u4E00\u5EA6\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044)"));
|
|
5566
|
+
break;
|
|
5567
|
+
case "binding-mismatch":
|
|
5568
|
+
out2(t("\u3053\u306E\u30D1\u30B9\u306B\u306F\u3001\u7D50\u3073\u4ED8\u3051\u305F\u3068\u304D\u3068\u306F\u5225\u306E\u8A08\u753B\u304C\u7F6E\u304B\u308C\u3066\u3044\u307E\u3059 (\u7D50\u3073\u4ED8\u3051\u305F\u8A08\u753B\u306E ID: {expected}\u3001\u4ECA\u306E\u8A08\u753B\u306E ID: {actual})\u3002\u540C\u671F\u3057\u307E\u305B\u3093", { expected: halt2.expected, actual: halt2.actual }));
|
|
5569
|
+
break;
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
async function runWatchCommand(o, out2, stop) {
|
|
5573
|
+
const first = await runSyncCommand({ ...o, watch: false }, out2);
|
|
5574
|
+
if (first === 1) return 1;
|
|
5575
|
+
const bound = bindingsOf(o.file).bindings;
|
|
5576
|
+
const server = o.server ?? process.env.BOXGLOW_SERVER ?? bound[0]?.server;
|
|
5577
|
+
if (!server) return 1;
|
|
5578
|
+
const lock = lockWatch(server);
|
|
5579
|
+
if (!lock.unlock) {
|
|
5580
|
+
out2(t("\u3053\u306E\u7AEF\u672B\u3067\u306F\u3001\u3059\u3067\u306B\u5E38\u6642\u306E\u540C\u671F\u304C\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u305D\u3061\u3089\u304C\u3001\u3053\u306E\u8A08\u753B\u3082\u53D7\u3051\u6301\u3061\u307E\u3059 (\u52D5\u3044\u3066\u3044\u306A\u3044\u306E\u306B\u6B8B\u3063\u3066\u3044\u308B\u3068\u304D\u306F: boxglow unlock --file {path})", { path: lock.path }));
|
|
5581
|
+
return 0;
|
|
5582
|
+
}
|
|
5583
|
+
const stamp = () => (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
|
|
5584
|
+
const onEvent = (e) => {
|
|
5585
|
+
if (e.kind === "synced") out2(`[${stamp()}] ${e.file}: ` + t("\u540C\u671F\u3057\u307E\u3057\u305F (\u53D7\u3051\u53D6\u308A {pulled} \u56DE\u3001\u9001\u308A {pushed} \u56DE)\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u7248: {revision}", { pulled: e.pulled, pushed: e.pushed, revision: e.revision ?? "-" }));
|
|
5586
|
+
else if (e.kind === "network") out2(`[${stamp()}] ` + t("\u30B5\u30FC\u30D0\u30FC\u3068\u901A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3002{seconds} \u79D2\u5F8C\u306B\u3084\u308A\u76F4\u3057\u307E\u3059: {message}", { seconds: Math.round(e.retryInMs / 1e3), message: e.message }));
|
|
5587
|
+
else if (e.kind === "auth") out2(`[${stamp()}] ` + t("\u30B5\u30FC\u30D0\u30FC\u304C\u5229\u7528\u8005\u3092\u78BA\u304B\u3081\u3089\u308C\u307E\u305B\u3093 (\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u3044\u3001\u307E\u305F\u306F\u7121\u52B9\u3067\u3059)\u3002\u5E38\u6642\u306E\u540C\u671F\u3092\u6B62\u3081\u305A\u306B\u5F85\u3061\u307E\u3059\u3002\u30C8\u30FC\u30AF\u30F3\u3092\u76F4\u3057\u3066\u304B\u3089\u3001\u8D77\u52D5\u3057\u76F4\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
5588
|
+
else if (e.kind === "error") out2(`[${stamp()}] ${e.file ? e.file + ": " : ""}` + t("\u3053\u306E\u8A08\u753B\u306E\u540C\u671F\u3092\u6B62\u3081\u3066\u3044\u307E\u3059: {message}", { message: e.message }));
|
|
5589
|
+
else {
|
|
5590
|
+
out2(`[${stamp()}] ${e.file}:`);
|
|
5591
|
+
describeHalt(e.result, e.file, (line) => out2(" " + line));
|
|
5592
|
+
out2(" " + t("(\u4E0A\u306E\u30B3\u30DE\u30F3\u30C9\u306F\u3001\u305D\u306E\u8A08\u753B\u306E\u30D5\u30A9\u30EB\u30C0\u3067\u3001\u5225\u306E\u7AEF\u672B\u753B\u9762\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u5E38\u6642\u306E\u540C\u671F\u306F\u52D5\u304B\u3057\u305F\u307E\u307E\u3067\u69CB\u3044\u307E\u305B\u3093)"));
|
|
5593
|
+
}
|
|
5594
|
+
};
|
|
5595
|
+
const watcher = new SyncWatcher({ server, token: resolveToken(server)?.token, onEvent });
|
|
5596
|
+
out2(t("\u5E38\u6642\u306E\u540C\u671F\u3092\u59CB\u3081\u307E\u3057\u305F (Ctrl+C \u3067\u7D42\u4E86)\u3002\u30B5\u30FC\u30D0\u30FC: {server}\u3001\u8A08\u753B: {count} \u4EF6", { server, count: bindingsFor(server).states.length }));
|
|
5597
|
+
const timer = setInterval(() => {
|
|
5598
|
+
watcher.tick().catch((e) => out2(`[${stamp()}] ` + String(e)));
|
|
5599
|
+
}, 1e3);
|
|
5600
|
+
try {
|
|
5601
|
+
await (stop ?? new Promise((done) => {
|
|
5602
|
+
process.once("SIGINT", () => done());
|
|
5603
|
+
process.once("SIGTERM", () => done());
|
|
5604
|
+
}));
|
|
5605
|
+
} finally {
|
|
5606
|
+
clearInterval(timer);
|
|
5607
|
+
lock.unlock();
|
|
5608
|
+
}
|
|
5609
|
+
const unsent = bindingsFor(server).states.map((s) => s.state).filter((s) => {
|
|
5610
|
+
try {
|
|
5611
|
+
return hashOf(readFileSync8(s.binding.file, "utf8")) !== s.base?.hash;
|
|
5612
|
+
} catch {
|
|
5613
|
+
return false;
|
|
5614
|
+
}
|
|
5615
|
+
});
|
|
5616
|
+
out2(t("\u5E38\u6642\u306E\u540C\u671F\u3092\u7D42\u3048\u307E\u3057\u305F\u3002"));
|
|
5617
|
+
for (const s of unsent) out2(t("\u307E\u3060\u9001\u3063\u3066\u3044\u306A\u3044\u5909\u66F4\u304C\u3042\u308A\u307E\u3059: {file} (boxglow sync \u3067\u9001\u308C\u307E\u3059)", { file: s.binding.file }));
|
|
5618
|
+
return 0;
|
|
5619
|
+
}
|
|
5620
|
+
function rejectionHint(status) {
|
|
5621
|
+
if (status === 413) return t("\u8A08\u753B\u304C\u5927\u304D\u3059\u304E\u307E\u3059");
|
|
5622
|
+
if (status === 426) return t("\u3053\u306E\u7248\u306E Boxglow \u306F\u53E4\u304F\u3001\u30B5\u30FC\u30D0\u30FC\u304C\u53D7\u3051\u4ED8\u3051\u307E\u305B\u3093\u3002\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
5623
|
+
if (status === 400) return t("\u30B5\u30FC\u30D0\u30FC\u304C\u3001\u8A08\u753B\u3068\u3057\u3066\u6B63\u3057\u304F\u306A\u3044\u3068\u5224\u65AD\u3057\u307E\u3057\u305F");
|
|
5624
|
+
if (status === 507) return t("\u4FDD\u5B58\u306E\u4E0A\u9650 (\u8A08\u753B\u306E\u6570\u3001\u307E\u305F\u306F\u4FDD\u5B58\u91CF) \u306B\u5C4A\u3044\u3066\u3044\u307E\u3059\u3002\u5C65\u6B74\u306E\u6574\u7406\u304B\u3001\u4F7F\u3063\u3066\u3044\u306A\u3044\u8A08\u753B\u306E\u524A\u9664\u304C\u8981\u308A\u307E\u3059");
|
|
5625
|
+
return t("\u8981\u6C42\u306E\u5F62\u304C\u5408\u3044\u307E\u305B\u3093");
|
|
5626
|
+
}
|
|
5627
|
+
function quote(value) {
|
|
5628
|
+
return /^[A-Za-z0-9_\-./:@]+$/.test(value) ? value : `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
5629
|
+
}
|
|
5630
|
+
|
|
5631
|
+
// src/model/resume.ts
|
|
5632
|
+
function resumeSummary(project, options = {}) {
|
|
5633
|
+
const s = summarize(project);
|
|
5634
|
+
const all = Object.entries(project.handoffs ?? {}).filter(([id, note]) => project.blocks[id] && note.note.trim()).map(([blockId, note]) => {
|
|
5635
|
+
const b = project.blocks[blockId];
|
|
5636
|
+
return {
|
|
5637
|
+
blockId,
|
|
5638
|
+
key: b.key,
|
|
5639
|
+
title: b.title,
|
|
5640
|
+
status: b.status,
|
|
5641
|
+
...note,
|
|
5642
|
+
freshness: noteFreshness(b, note.at),
|
|
5643
|
+
freshnessText: freshnessText(b, note.at),
|
|
5644
|
+
statusChangedAt: b.statusChangedAt
|
|
5645
|
+
};
|
|
5646
|
+
}).sort((a, b) => b.at.localeCompare(a.at) || a.blockId.localeCompare(b.blockId));
|
|
5647
|
+
const nextGroups = candidateGroups(project, s.next).map((g) => ({ ...g, items: g.items.slice(0, 10) }));
|
|
5648
|
+
return {
|
|
5649
|
+
project: project.name,
|
|
5650
|
+
focus: project.focusBlockId ? { blockId: project.focusBlockId, title: project.blocks[project.focusBlockId]?.title } : void 0,
|
|
5651
|
+
active: [...s.working, ...s.blocked].map(({ block: block2, actor, note }) => ({ blockId: block2.id, key: block2.key, title: block2.title, actor, note })),
|
|
5652
|
+
pendingDecisions: s.decisions.map(({ block: block2, decision: decision2 }) => ({ key: block2.key, title: block2.title, question: decision2.question })),
|
|
5653
|
+
unreadAnswers: s.answered.map(({ block: block2, decision: decision2 }) => ({ blockId: block2.id, key: block2.key, title: block2.title, question: decision2.question, answer: decision2.answer, answeredAt: decision2.answeredAt })),
|
|
5654
|
+
nextGroups,
|
|
5655
|
+
// 既存の利用側が next を参照していても読めるよう、平らな一覧も残す。
|
|
5656
|
+
next: nextGroups.flatMap((g) => g.items),
|
|
5657
|
+
handoffs: all.filter((h) => h.status !== "white" || options.includeCompleted),
|
|
5658
|
+
completedHandoffCount: all.filter((h) => h.status === "white").length,
|
|
5659
|
+
descriptionReminders: Object.values(project.blocks).filter((b) => descriptionReminder(b)).map((b) => ({ blockId: b.id, key: b.key, title: b.title, message: descriptionReminder(b), writtenAt: b.descriptionUpdatedAt })),
|
|
5660
|
+
instructions: t("\u4F5C\u696D\u306E\u524D\u306B boxglow context <block> \u3092\u8AAD\u307F\u3001guard \u4ED8\u304D\u306E\u5909\u66F4\u306B\u306F\u305D\u306E contextToken \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002\u3053\u306E\u6982\u8981\u306F\u56DE\u7B54\u3092\u78BA\u8A8D\u6E08\u307F\u306B\u305B\u305A\u3001\u64CD\u4F5C\u3092\u8A31\u53EF\u3059\u308B\u3082\u306E\u3067\u3082\u3042\u308A\u307E\u305B\u3093\u3002")
|
|
5661
|
+
};
|
|
5662
|
+
}
|
|
5663
|
+
function resumeReport(project, options = {}) {
|
|
5664
|
+
const s = resumeSummary(project, options);
|
|
5665
|
+
return [
|
|
5666
|
+
"# " + s.project + " \u2014 " + t("\u518D\u958B"),
|
|
5667
|
+
// 今回の範囲のボックスが完了していたら、候補の優先がもう意味を持たないので、選び直しを促す
|
|
5668
|
+
...s.focus ? [t("\u4ECA\u56DE\u306E\u7BC4\u56F2: {title}", { title: s.focus.title ?? "" }) + (project.blocks[s.focus.blockId]?.status === "white" ? " \u2014 " + t("\u5B8C\u4E86\u6E08\u307F\u3067\u3059\u3002focus \u3067\u6B21\u306E\u5BFE\u8C61\u3092\u9078\u3076\u304B\u3001focus none \u3067\u89E3\u9664\u3057\u3066\u304F\u3060\u3055\u3044") : "")] : [],
|
|
5669
|
+
"## " + t("\u4F5C\u696D\u4E2D\u30FB\u78BA\u8A8D\u5F85\u3061"),
|
|
5670
|
+
...s.active.map((a) => "- " + a.key + " " + a.title + " (" + a.actor + "): " + (a.note ?? "")),
|
|
5671
|
+
"## " + t("\u5224\u65AD\u5F85\u3061"),
|
|
5672
|
+
...s.pendingDecisions.map((a) => "- " + a.key + " " + a.title + ": " + a.question),
|
|
5673
|
+
"## " + t("AI\u672A\u78BA\u8A8D\u306E\u56DE\u7B54"),
|
|
5674
|
+
...s.unreadAnswers.map((a) => "- " + a.key + " " + a.title + "\n " + a.question + "\n \u2192 " + a.answer),
|
|
5675
|
+
"## " + t("\u6B21\u306E\u5019\u88DC"),
|
|
5676
|
+
...s.nextGroups.flatMap((g) => ["### " + g.title, ...g.items.map((a) => "- " + a.key + " " + a.title + (a.missingInputs.length ? " \u2014 " + t("\u5FC5\u9808\u306E\u5165\u529B\u5F85\u3061: {names}", { names: a.missingInputs.join(", ") }) : ""))]),
|
|
5677
|
+
"## " + t("\u5F15\u304D\u7D99\u304E\uFF08\u65B0\u3057\u3044\u9806\uFF09"),
|
|
5678
|
+
...s.handoffs.map((h) => "- " + h.key + " " + h.title + " (" + h.actor + ", " + (h.status === "white" ? "Done" : h.status === "gray" ? "In Progress" : "New") + ")\n " + h.freshnessText + "\n" + h.note),
|
|
5679
|
+
...!options.includeCompleted && s.completedHandoffCount ? [t("\u5B8C\u4E86\u6E08\u307F\u306E\u5F15\u304D\u7D99\u304E {n} \u4EF6\u3002resume --include-completed \u3067\u8868\u793A\u3067\u304D\u307E\u3059\u3002", { n: s.completedHandoffCount })] : [],
|
|
5680
|
+
...s.descriptionReminders.length ? [t("\u8AAC\u660E\u306E\u898B\u76F4\u3057\u5019\u88DC: {names} (show \u3067\u78BA\u8A8D)", { names: s.descriptionReminders.map((x) => x.key ?? x.title).join(", ") })] : [],
|
|
5681
|
+
"",
|
|
5682
|
+
s.instructions
|
|
5683
|
+
].join("\n");
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5686
|
+
// cli/main.ts
|
|
5687
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5688
|
+
|
|
5689
|
+
// docs/AGENTS_SNIPPET.md
|
|
5690
|
+
var AGENTS_SNIPPET_default = '# Boxglow \u3092\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u4F7F\u308F\u305B\u308B\u6307\u793A\u66F8 (AGENTS.md / CLAUDE.md \u306B\u8CBC\u308B)\n\n\u4E0B\u306E\u300C---\u300D\u306E\u9593\u3092\u305D\u306E\u307E\u307E\u30EA\u30DD\u30B8\u30C8\u30EA\u306E AGENTS.md (Codex) \u3084 CLAUDE.md (Claude Code) \u306B\u8CBC\u308B\u3002\nboxglow.json \u306F\u30EA\u30DD\u30B8\u30C8\u30EA\u76F4\u4E0B\u306B\u7F6E\u304F (`npx boxglow init --name "<\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D>"`)\u3002\n\n---\n\n## Boxglow (\u8A08\u753B\u3068\u9032\u6357\u306E\u5171\u6709)\n\n\u3053\u306E\u30EA\u30DD\u30B8\u30C8\u30EA\u306E\u8A08\u753B\u3068\u9032\u6357\u306F `boxglow.json` \u304C\u6B63\u672C\u3002\u4EBA\u9593\u306F Boxglow \u306E\u753B\u9762\u3067\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u898B\u3066\u3044\u308B\u3002\n\u30D5\u30A1\u30A4\u30EB\u3092\u76F4\u63A5\u7DE8\u96C6\u305B\u305A\u3001\u5FC5\u305A `npx boxglow` \u30B3\u30DE\u30F3\u30C9\u3067\u66F4\u65B0\u3059\u308B\u3053\u3068 (\u7D50\u7DDA\u30EB\u30FC\u30EB\u3068\u5C65\u6B74\u304C\u4FDD\u305F\u308C\u308B)\u3002\n\n\u30D6\u30ED\u30C3\u30AF = \u300C\u5165\u529B\u304B\u3089\u51FA\u529B (\u6210\u679C\u7269) \u3092\u4F5C\u308B\u30BF\u30B9\u30AF\u300D\u3002\u51FA\u529B\u306E\u6210\u679C\u7269\u304C\u78BA\u5B9A\u3057\u305F\u3089\u5B8C\u4E86 (WhiteBox)\u3002\n\u72B6\u614B: BlackBox (\u51FA\u529B\u3060\u3051\u6C7A\u3081\u305F) \u2192 GrayBox (\u5206\u89E3\u4E2D\u30FB\u7740\u624B\u4E2D) \u2192 WhiteBox (\u5B8C\u4E86)\u3002\n\n### \u4F5C\u696D\u306E\u305F\u3073\u306B\u5B88\u308B\u3053\u3068\n\n1. **\u6700\u521D\u306B\u8AAD\u3080**: `npx boxglow resume` (\u307E\u305F\u306F `npx boxglow status --brief`) \u3067\u3001\u4F5C\u696D\u4E2D\u30FB\u5224\u65AD\u5F85\u3061\u30FBAI \u672A\u78BA\u8A8D\u306E\u56DE\u7B54\u30FB\u6B21\u306E\u5019\u88DC\u30FB\u672A\u5B8C\u4E86\u306E\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u8AAD\u3080\u3002\u524D\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u7D9A\u304D\u306F\u3001\u3053\u3053\u306B\u51FA\u308B\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u304B\u3089\u518D\u958B\u3059\u308B\u3002`npx boxglow show <block>` \u3067\u62C5\u5F53\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u306E\u5165\u51FA\u529B\u3092\u78BA\u8A8D\u3059\u308B\n2. **\u7740\u624B\u3092\u8A18\u9332**: \u30DC\u30C3\u30AF\u30B9\u306B\u53D6\u308A\u304B\u304B\u308B\u3068\u304D `npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>"`\u3002\u540C\u6642\u306B\u9032\u3081\u308B\u30DC\u30C3\u30AF\u30B9\u306F 1\u301C2 \u500B\u307E\u3067\u3002\n \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u304C\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001\u5148\u306B `npx boxglow context <block>` \u3092\u8AAD\u307F\u3001\u51FA\u529B\u306E `contextToken` (\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3) \u3092\u4ED8\u3051\u308B:\n `npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` (\u4E0B\u306E\u300C\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3068\u5F15\u304D\u7D99\u304E\u300D\u3092\u53C2\u7167)\n3. **\u5206\u89E3**: 1 \u3064\u306E\u30DC\u30C3\u30AF\u30B9\u3067\u51FA\u529B\u3092\u4F5C\u308B\u898B\u901A\u3057\u304C\u7ACB\u305F\u306A\u3044\u3068\u304D\u306F\u3001`npx boxglow split <block> --spec \'<JSON>\'` \u3067\u4E2D\u306B\u5C0F\u3055\u306A\u30DC\u30C3\u30AF\u30B9\u3092\u7F6E\u304F\u3002\u5404\u30DC\u30C3\u30AF\u30B9\u306B\u51FA\u529B (\u6210\u679C\u7269) \u3092\u5FC5\u305A\u6C7A\u3081\u308B\u3002\u5165\u529B\u306F\u4E0D\u660E\u306A\u3089\u7701\u7565\u3057\u3066\u3088\u3044 (\u81EA\u52D5\u3067\u4E0A\u306E\u968E\u5C64\u306E\u5165\u529B\u306B\u306A\u308B)\n4. **\u5B8C\u4E86\u3092\u8A18\u9332**: \u51FA\u529B\u306E\u6210\u679C\u7269\u304C\u3067\u304D\u305F\u3089 `npx boxglow done <block> --artifact "<\u540D\u524D>=<\u30EA\u30DD\u30B8\u30C8\u30EA\u5185\u306E\u30D1\u30B9>"`\u3002\n guard \u304C\u6709\u52B9\u306A\u3089 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3092\u4ED8\u3051\u308B (\u76F4\u524D\u306E\u81EA\u5206\u306E\u64CD\u4F5C\u306E\u51FA\u529B\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u3066\u3044\u308C\u3070\u3001\u305D\u308C\u3092\u4F7F\u3046)\u3002\n HEAD \u306B\u30B3\u30DF\u30C3\u30C8\u6E08\u307F\u3067\u5185\u5BB9\u304C\u4E00\u81F4\u3059\u308B\u30D5\u30A1\u30A4\u30EB\u306F\u300C\u30B3\u30DF\u30C3\u30C8 + \u30D1\u30B9 + \u4E2D\u8EAB\u306E\u30CF\u30C3\u30B7\u30E5\u300D\u3067\u8A18\u9332\u3055\u308C\u308B (\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306F\u3057\u306A\u3044)\u3002\u672A\u30B3\u30DF\u30C3\u30C8\u30FB\u672A\u8FFD\u8DE1\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u30ED\u30FC\u30AB\u30EB\u53C2\u7167\u3068\u3057\u3066\u6B8B\u308A\u3001\u30B3\u30DF\u30C3\u30C8\u3057\u305F\u5F8C\u306E `npx boxglow check` \u3067 Git \u306E\u53C2\u7167\u306B\u88DC\u5B8C\u3055\u308C\u308B\u3002\u8A18\u9332\u306E\u305F\u3081\u3060\u3051\u306B\u30B3\u30DF\u30C3\u30C8\u3057\u306A\u3044\u3002\u5168\u968E\u5C64\u304C\u5FC5\u8981\u306A\u3089 `npx boxglow status` \u3092\u8AAD\u3080\u3002PR \u3084\u5916\u90E8\u8CC7\u6599\u306F URL \u3067\u3082\u3088\u3044\u3002\n \u6210\u679C\u7269\u3060\u3051\u5148\u306B\u4ED8\u3051\u308B\u306A\u3089 `npx boxglow artifact <block> <\u30D1\u30B9>`\u3002\u9032\u6357\u306E\u9014\u4E2D\u7D4C\u904E\u306F `npx boxglow set <block> --progress 60`\n5. **\u4EBA\u9593\u306E\u5224\u65AD\u304C\u8981\u308B**: `npx boxglow ask <block> "<\u8CEA\u554F>" --options "A|B"`\u3002\u56DE\u7B54\u304C\u3042\u308B\u307E\u3067\u305D\u306E\u30DC\u30C3\u30AF\u30B9\u306F\u9032\u3081\u305A\u3001\u4ED6\u306E\u30DC\u30C3\u30AF\u30B9\u3078\u79FB\u308B\u3002\u4EBA\u306E\u56DE\u7B54\u306F `status` \u306E\u300C\u56DE\u7B54\u3042\u308A\u300D\u306B\u51FA\u308B\u3002\u8AAD\u3093\u3060\u3089 `npx boxglow ack <block>` \u3067\u5F15\u304D\u53D6\u308B (\u305D\u306E\u30DC\u30C3\u30AF\u30B9\u306E `start` / `done` / `set` \u306A\u3069\u3067\u3082\u81EA\u52D5\u3067\u5F15\u304D\u53D6\u3089\u308C\u308B)\u3002\u5F15\u304D\u53D6\u308B\u307E\u3067\u4EBA\u306E\u753B\u9762\u306B\u306F\u300CAI \u672A\u78BA\u8A8D\u300D\u3068\u3057\u3066\u6B8B\u308A\u3001\u4EBA\u304C\u7B54\u3048\u3092\u76F4\u305B\u308B\u3002\u56DE\u7B54\u306E\u4E2D\u306B\u554F\u3044\u8FD4\u3057\u304C\u3042\u308C\u3070\u3001`ask` \u3068 `answer --by <\u81EA\u5206>` \u3067\u81EA\u5206\u306E\u7B54\u3048\u3082\u8A18\u9332\u3059\u308B\n6. **\u8A70\u307E\u3063\u305F\u3089**: `npx boxglow blocked <block> --note "<\u56F0\u3063\u3066\u3044\u308B\u3053\u3068>"`\n7. **\u6210\u679C\u7269\u306E\u78BA\u8A8D**: \u30D5\u30A1\u30A4\u30EB\u3092\u79FB\u52D5\u30FB\u6539\u540D\u3057\u305F\u3089 `npx boxglow check` \u3092\u5B9F\u884C\u3059\u308B (\u79FB\u52D5\u3092\u691C\u51FA\u3057\u3066\u30D1\u30B9\u3092\u4ED8\u3051\u66FF\u3048\u308B\u3002\u898B\u3064\u304B\u3089\u306A\u3051\u308C\u3070\u5370\u304C\u4ED8\u304F)\n8. **\u4E2D\u65AD\u30FB\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B**: \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u7D42\u308F\u308A\u30FB\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u5727\u7E2E\u30FB\u4ED6\u306E AI \u3084\u4EBA\u3078\u306E\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B\u3001`npx boxglow checkpoint <block> --note "\u5206\u304B\u3063\u305F\u3053\u3068; \u6B21\u306B\u3059\u308B\u3053\u3068; \u672A\u89E3\u6C7A\u306E\u3053\u3068"` \u3067\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u6B8B\u3059 (guard \u304C\u6709\u52B9\u306A\u3089 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3092\u4ED8\u3051\u308B)\u3002\u4F1A\u8A71\u306E\u5C65\u6B74\u304C\u6D88\u3048\u3066\u3082\u8A08\u753B\u306E\u4E2D\u306B\u6B8B\u308A\u3001\u6B21\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u304C `resume` \u3068 `context` \u3067\u8AAD\u3080\n9. **\u5831\u544A**: \u4F5C\u696D\u306E\u6700\u5F8C\u306B `npx boxglow status --brief` \u306E\u5185\u5BB9\u3092\u8981\u7D04\u3057\u3066\u5831\u544A\u3059\u308B\n\n### \u69CB\u9020\u306E\u7D04\u675F\n\n- \u6700\u4E0A\u4F4D\u306B\u306F\u300C\u5165\u529B\u30CE\u30FC\u30C9\u300D\u300C\u6700\u7D42\u6210\u679C\u7269\u30CE\u30FC\u30C9\u300D\u3068\u300C\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u300D\u3060\u3051\u304C\u3042\u308B\u3002\u30BF\u30B9\u30AF\u306F\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u4E2D\u306B\u7F6E\u304F (`add` \u306E\u65E2\u5B9A\u306E\u89AA\u306F\u6700\u521D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9)\n- \u968E\u5C64\u306E\u6DF1\u3055\u306B\u5236\u9650\u306F\u306A\u3044\u3002`split` \u306F\u4F55\u6BB5\u3067\u3082\u4F7F\u3048\u308B\n- \u5225\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u3082\u4F7F\u3048\u305D\u3046\u306A\u30DC\u30C3\u30AF\u30B9 (\u4F8B: \u5165\u529B\u753B\u50CF\u306E\u30E2\u30CE\u30AF\u30ED\u5316) \u306F `npx boxglow export-block <block> --out <\u540D\u524D>.boxglow-block.json` \u3067\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306B\u3057\u3001\n `npx boxglow import-block <path> --parent <block>` \u3067\u633F\u5165\u3059\u308B\u3002\u30EA\u30DD\u30B8\u30C8\u30EA\u306E `boxglow-blocks/` \u306B\u7F6E\u3044\u3066\u5171\u6709\u3057\u3066\u3088\u3044\n\n\u6700\u4E0A\u4F4D\u306E\u5165\u529B\u304C\u591A\u3044\u3068\u304D\u306F `npx boxglow group "<\u30B0\u30EB\u30FC\u30D7\u540D>"` \u3067\u30B0\u30EB\u30FC\u30D7\u3092\u4F5C\u308A\u3001`npx boxglow group-set <\u5165\u529B\u540D> <\u30B0\u30EB\u30FC\u30D7\u540D>` \u3067\u5206\u3051\u308B (\u4F8B: PCIe \u4ED5\u69D8\u66F8 / DDR \u4ED5\u69D8\u66F8)\u3002\n\n### split \u306E JSON \u5F62\u5F0F\n\n```json\n{\n "blocks": [\n { "title": "\u8A2D\u8A08", "description": "\u3084\u308B\u3053\u3068", "inputs": ["\u4ED5\u69D8"], "outputs": ["\u8A2D\u8A08\u66F8"] },\n { "title": "\u5B9F\u88C5", "inputs": ["\u8A2D\u8A08\u66F8"], "outputs": ["\u30B3\u30FC\u30C9"] }\n ],\n "connections": [\n { "from": "parent.\u4ED5\u69D8", "to": "\u8A2D\u8A08.\u4ED5\u69D8" },\n { "from": "\u8A2D\u8A08.\u8A2D\u8A08\u66F8", "to": "\u5B9F\u88C5.\u8A2D\u8A08\u66F8" },\n { "from": "\u5B9F\u88C5.\u30B3\u30FC\u30C9", "to": "parent.\u51FA\u529B" }\n ]\n}\n```\n\n`parent.<\u540D\u524D>` \u306F\u5206\u89E3\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u81EA\u8EAB\u306E\u5165\u529B / \u51FA\u529B\u3002`<block>` \u306F\u77ED\u3044 ID (B12 \u306A\u3069\u3002status \u306B\u51FA\u308B) \u304B\u984C\u540D\u3002\n\u4E0B\u306E\u968E\u5C64\u3092\u6301\u305F\u306A\u3044\u30DC\u30C3\u30AF\u30B9\u306E\u51FA\u529B\u306F 1 \u672C (outputs \u306F 1 \u3064\u3060\u3051\u66F8\u304F)\u3002\u984C\u540D\u306F\u300C\u4F55\u3092\u4F5C\u308B\u304B\u300D\u304C\u5206\u304B\u308B\u77ED\u3044\u8A00\u8449\u306B\u3059\u308B\u3002\n\u671F\u65E5\u3084\u6642\u9593\u306F `npx boxglow set <block> --due 2026-10-15 --start 2026-10-01 --estimate 8 --hours 3.5` \u3067\u8A18\u9332\u3059\u308B\u3002\n\u30DC\u30C3\u30AF\u30B9\u306B\u306F\u4ED5\u4E8B\u306E\u7A2E\u985E (\u30AB\u30C6\u30B4\u30EA) \u3092\u4ED8\u3051\u308B: `--category design` (\u8A2D\u8A08) / build (\u5B9F\u88C5) / verify (\u691C\u8A3C) / evaluate (\u8A55\u4FA1) / study (\u691C\u8A0E) / research (\u8ABF\u67FB) / ui (\u30C7\u30B6\u30A4\u30F3) / improve (\u6539\u5584) / fix (\u8AB2\u984C\u89E3\u6C7A) / docs (\u6587\u66F8) / ops (\u904B\u7528) / other (\u305D\u306E\u4ED6: \u8FF7\u3063\u305F\u3068\u304D)\u3002add \u306E\u3068\u304D\u306B\u4ED8\u3051\u3001\u5909\u3048\u308B\u306A\u3089 `npx boxglow set <block> --category verify`\u3002\n\u7D50\u7DDA\u306E\u53D7\u3051\u5074\u306F\u984C\u540D\u3060\u3051\u3067\u3088\u3044 (`npx boxglow connect "A.\u8A2D\u8A08\u66F8" "B"` \u3067 B \u306B\u5165\u529B\u300C\u8A2D\u8A08\u66F8\u300D\u304C\u4F5C\u3089\u308C\u3066\u3064\u306A\u304C\u308B\u3002split \u306E connections \u3082 `to: "B"` \u3067\u3088\u3044)\u3002\u3064\u306A\u3044\u3060\u5165\u529B\u306E\u540D\u524D\u306F\u4F9B\u7D66\u5143\u306E\u51FA\u529B\u540D\u306B\u306A\u308A\u3001\u5165\u529B\u5074\u3067\u306F\u5909\u3048\u3089\u308C\u306A\u3044 (\u5909\u3048\u308B\u306A\u3089\u4F9B\u7D66\u5143\u306E\u51FA\u529B\u306E\u540D\u524D\u3092\u5909\u3048\u308B\u3002\u3064\u306A\u304C\u308B\u5148\u3082\u4E00\u7DD2\u306B\u5909\u308F\u308B)\u3002\u65E2\u5B58\u306E\u30DC\u30C3\u30AF\u30B9\u306B\u5165\u51FA\u529B\u3092\u8DB3\u3059 / \u540D\u524D\u3092\u5909\u3048\u308B\u306E\u306F `npx boxglow port <block> --in <\u540D\u524D> --out <\u540D\u524D> --rename <\u65E7>=<\u65B0>` (`project` = \u6700\u521D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u3002\u6700\u7D42\u6210\u679C\u7269\u306E\u540D\u524D\u3082\u3053\u308C\u3067\u5909\u3048\u308B)\u3002\u7DDA\u3092\u5916\u3059\u306E\u306F `npx boxglow disconnect <\u984C\u540D.\u51FA\u529B\u540D> <\u984C\u540D.\u5165\u529B\u540D>`\u3001\u30DC\u30C3\u30AF\u30B9\u305D\u306E\u3082\u306E\u3092\u6D88\u3059\u306E\u306F `npx boxglow remove <block>` (\u4E2D\u306B\u30DC\u30C3\u30AF\u30B9\u304C\u3042\u308C\u3070 `--force`)\u3002MCP \u30C4\u30FC\u30EB (`boxglow_status` / `boxglow_start` / `boxglow_done` / `boxglow_ask` \u306A\u3069\u3002\u5229\u7528\u3059\u308B\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u767B\u9332\u6E08\u307F\u306A\u3089\u4F7F\u3048\u308B\u3002`.mcp.json` \u306F Claude Code \u5411\u3051\u3067\u3001Codex \u306F\u5225\u9014\u767B\u9332\u3059\u308B) \u306F\u540C\u3058\u30B3\u30DE\u30F3\u30C9\u306E\u5225\u306E\u5165\u53E3\u3067\u3001\u3069\u3061\u3089\u3067\u66F8\u3044\u3066\u3082\u540C\u3058\u30D5\u30A1\u30A4\u30EB\u304C\u66F4\u65B0\u3055\u308C\u308B\u3002\u8A08\u753B\u306E\u6587\u66F8\u306F `npx boxglow export --out docs/ROADMAP.md`\u3002\n\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u306F\u3001\u65B9\u91DD (README \u3084\u4F9D\u983C\u6587) \u304B\u3089\u6700\u4E0A\u4F4D\u306E\u5927\u9805\u76EE 3\u301C7 \u500B\u3092\u5148\u306B\u4F5C\u308A\u3001\u4EBA\u306B `ask` \u3067\u78BA\u8A8D\u3057\u3066\u304B\u3089\u3001\u7740\u624B\u3059\u308B\u5927\u9805\u76EE\u3060\u3051\u3092 `split` \u3067\u5206\u89E3\u3059\u308B (\u5148\u306E\u6BB5\u968E\u306F\u7C97\u3044\u307E\u307E)\u3002`ask` \u306E\u8CEA\u554F\u306F\u305D\u308C\u3060\u3051\u3067\u5224\u65AD\u3067\u304D\u308B\u3088\u3046\u306B\u66F8\u304D\u3001\u524D\u63D0\u30FB\u6BD4\u8F03\u30FB\u5F71\u97FF\u306F `--context` \u306B\u5165\u308C\u308B (\u300C\u3053\u308C\u3067\u3088\u3044\u3067\u3059\u304B\u300D\u306E\u3088\u3046\u306B\u5916\u3092\u6307\u3055\u306A\u3044)\u3002\u5019\u88DC\u304B\u3089 1 \u3064\u3092\u9078\u3076\u5834\u9762\u3067\u306F\u3001\u81EA\u5206\u3067\u6C7A\u3081\u308B\u3068\u304D\u3082 `ask ... --options "A|B|C"` \u3068 `answer --by <\u81EA\u5206>` \u3067\u8A18\u9332\u3057\u3001\u9078\u3070\u306A\u304B\u3063\u305F\u5019\u88DC\u3092\u6B8B\u3059\u3002\u65B9\u91DD\u8EE2\u63DB\u306F `reopen <block> --note <\u7406\u7531>`\u3002\u3059\u3079\u3066 CLI \u3060\u3051\u3067\u6210\u7ACB\u3059\u308B (\u753B\u9762\u306F\u4EBA\u304C\u898B\u308B\u305F\u3081\u306E\u3082\u306E)\u3002\u4EBA\u304C\u3044\u306A\u3044\u904B\u7528\u306A\u3089\u3001AI \u304C `answer --by <\u81EA\u5206>` \u3067\u5224\u65AD\u3092\u8A18\u9332\u3057\u3066\u9032\u3081\u308B\u3002\n\u51FA\u529B\u306E\u540D\u524D\u306F\u300C\u5177\u4F53\u7684\u306A\u6210\u679C\u7269\u300D\u306B\u3059\u308B (\u4F8B: `\u8A2D\u8A08\u66F8 docs/design.md`\u3001`PR #12`\u3001`\u516C\u958B URL`\u3001`\u30C6\u30B9\u30C8\u7D50\u679C (vitest 53 \u4EF6)`)\u3002\u300C\u6A5F\u80FD\u4E00\u5F0F\u300D\u300C\u6240\u898B\u300D\u306E\u3088\u3046\u306A\u62BD\u8C61\u7684\u306A\u540D\u524D\u306F\u907F\u3051\u3001done \u306E\u3068\u304D\u306F\u5FC5\u305A --artifact \u3067\u30D5\u30A1\u30A4\u30EB\u30FBURL\u30FB\u30B3\u30DF\u30C3\u30C8\u3092\u4ED8\u3051\u308B\u3002\n\u3059\u3079\u3066\u306E\u30DC\u30C3\u30AF\u30B9\u306F\u300C\u5165\u529B \u2192 \u51FA\u529B\u300D\u304C\u4F55\u304B\u306B\u3064\u306A\u304C\u3063\u3066\u3044\u308B\u3088\u3046\u306B\u3059\u308B (\u3064\u306A\u304C\u3063\u3066\u3044\u306A\u3044\u30DC\u30C3\u30AF\u30B9\u306F\u8A08\u753B\u306E\u7A74\u3002\u6700\u4E0A\u4F4D\u306E\u5165\u529B\u30CE\u30FC\u30C9\u306F\u3001\u672A\u63A5\u7D9A\u306E\u5165\u529B\u304C\u81EA\u52D5\u3067\u4E0A\u304C\u308B)\u3002\n\u8907\u6570\u4EBA\u3067\u540C\u3058 boxglow.json \u3092\u7DE8\u96C6\u3059\u308B\u306A\u3089\u3001\u5404\u81EA\u306E clone \u3067\u4E00\u5EA6 `npx boxglow git-setup` \u3092\u5B9F\u884C\u3059\u308B (\u30DC\u30C3\u30AF\u30B9\u306E\u5358\u4F4D\u3067\u81EA\u52D5\u30DE\u30FC\u30B8\u3055\u308C\u3001\u5225\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u5909\u66F4\u306F\u885D\u7A81\u3057\u306A\u3044)\u3002\n\u8907\u6570\u30EA\u30DD\u30B8\u30C8\u30EA\u3092 1 \u3064\u306E\u8A08\u753B\u3067\u7BA1\u7406\u3059\u308B\u3068\u304D\u306F\u3001boxglow.json \u3092\u4E0A\u306E\u30D5\u30A9\u30EB\u30C0\u306B\u7F6E\u304D\u3001\u5404\u30EA\u30DD\u30B8\u30C8\u30EA\u3067\u306F\u74B0\u5883\u5909\u6570 BOXGLOW_FILE \u306B\u305D\u306E\u5834\u6240\u3092\u5165\u308C\u308B (\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30DC\u30C3\u30AF\u30B9\u306F\u30EA\u30DD\u30B8\u30C8\u30EA\u3054\u3068\u306B 1 \u3064\u3002`npx boxglow project "\u540D\u524D" --repo <\u30D1\u30B9>`)\u3002\n\u5165\u529B\u306F\u65E2\u5B9A\u3067\u5FC5\u9808\u3002\u7121\u304F\u3066\u3082\u7740\u624B\u3067\u304D\u308B\u5165\u529B\u306F\u753B\u9762\u3067\u300C\u4EFB\u610F\u300D\u306B\u3059\u308B\u3002status \u306E\u300C\u6B21\u306E\u5019\u88DC\u300D\u306F\u5FC5\u9808\u306E\u5165\u529B\u304C\u305D\u308D\u3063\u305F\u30DC\u30C3\u30AF\u30B9\u304B\u3089\u4E26\u3076 (\u300C\u7740\u624B\u3067\u304D\u308B\u300D/\u300C\u5FC5\u9808\u306E\u5165\u529B\u5F85\u3061\u300D\u304C\u4ED8\u304F)\u3002\n\u5165\u529B\u306E\u8AAC\u660E\u306F\u66F8\u304B\u306A\u3044 (\u3064\u306A\u304C\u3063\u305F\u51FA\u529B\u5074\u306E\u8AAC\u660E\u304C\u4F7F\u308F\u308C\u308B)\u3002\u51FA\u529B\u306E\u8AAC\u660E\u306B\u5F62\u5F0F\u30FB\u5236\u7D04\u3092\u66F8\u304F\u3002\n\n\n### \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3068\u5F15\u304D\u7D99\u304E\n\n\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u306F\u3001AI \u304C\u53E4\u3044\u6307\u793A\u306E\u307E\u307E\u4F5C\u696D\u3092\u9032\u3081\u308B\u306E\u3092\u9632\u3050\u4ED5\u7D44\u307F\u3002`setup-agent` \u304C\u6709\u52B9\u306B\u3059\u308B (\u305D\u308C\u4EE5\u5916\u306E\u8A08\u753B\u3067\u306F `npx boxglow guard on`)\u3002\n\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001AI \u306E `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` \u306B `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u304C\u8981\u308B\u3002\u4EBA\u306E\u64CD\u4F5C (`--actor human`) \u306B\u306F\u8981\u6C42\u3055\u308C\u306A\u3044\u3002\n\n```\nnpx boxglow context B12 # \u5224\u65AD (\u5F15\u304D\u53D6\u308A\u6E08\u307F\u306E\u56DE\u7B54\u3092\u542B\u3080)\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u8AAD\u3080\u3002\u51FA\u529B\u306E contextToken \u304C\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\nnpx boxglow start B12 --note "<\u4F55\u3092\u3059\u308B\u304B>" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\nnpx boxglow ask B12 "<\u8CEA\u554F>" --options "A|B" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n# \u2192 \u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C: \u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\nnpx boxglow done B12 --artifact "<\u540D\u524D>=<\u30D1\u30B9>" --context-token <\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n```\n\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u308B\u5834\u5408 (\u8AAD\u307F\u76F4\u3057\u304C\u8981\u308B)**: \u4EBA\u304C\u56DE\u7B54\u3057\u305F\u30FB\u56DE\u7B54\u3092\u76F4\u3057\u305F\u3001\u4EBA\u304C\u6307\u793A (\u984C\u540D\u30FB\u8AAC\u660E\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6) \u3092\u5909\u3048\u305F\u3001\u4ED6\u306E AI \u3084\u4EBA\u304C\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u66F4\u65B0\u3057\u305F\u3002\u89AA\u3084\u5165\u529B\u5143\u306E\u30DC\u30C3\u30AF\u30B9\u3067\u8D77\u304D\u305F\u5834\u5408\u3082\u540C\u3058\u3002\u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u62D2\u5426\u3055\u308C\u308B\u306E\u3067\u3001`context` \u3092\u8AAD\u307F\u76F4\u3057\u3001\u5909\u308F\u3063\u305F\u5185\u5BB9\u3092\u4F5C\u696D\u306B\u53CD\u6620\u3057\u3066\u304B\u3089\u3084\u308A\u76F4\u3059\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u3089\u306A\u3044\u5834\u5408**: `start` / `blocked` / `review` / `leave` / `ack`\u3001\u9032\u6357\u30FB\u72B6\u614B\u30FB\u671F\u65E5\u30FB\u30AB\u30C6\u30B4\u30EA\u306E\u5909\u66F4\u3001`check` \u306B\u3088\u308B\u6210\u679C\u7269\u306E\u78BA\u8A8D\u3001\u753B\u9762\u3067\u306E\u914D\u7F6E\u306E\u5909\u66F4\u3002\u540C\u3058\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u3044\u7D9A\u3051\u3089\u308C\u308B\n- **\u81EA\u5206\u3067\u66F4\u65B0\u3067\u304D\u308B\u5834\u5408**: \u81EA\u5206\u306E `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` \u306A\u3069\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5909\u308F\u3063\u305F\u3068\u304D\u306F\u3001\u305D\u306E\u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u308B\u3002\u81EA\u5206\u3067\u5909\u3048\u305F\u5185\u5BB9\u306F\u5206\u304B\u3063\u3066\u3044\u308B\u306E\u3067\u3001\u8AAD\u307F\u76F4\u3055\u305A\u306B\u6B21\u306E\u64CD\u4F5C\u3078\u4F7F\u3046\u3002`ask` / `decision` / `answer` / `reopen` \u306F\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u7121\u3057\u3067\u3082\u5B9F\u884C\u3067\u304D\u308B\u304C\u3001\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4ED8\u3051\u305F\u3068\u304D\u3060\u3051\n- **\u6700\u521D\u306F\u77ED\u3044\u5F62\u3067\u8AAD\u3080**: `npx boxglow context <block> --brief` \u306F\u3001\u5BFE\u8C61\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u60C5\u5831 (\u8AAC\u660E\u30FB\u7BC4\u56F2\u30FB\u5224\u65AD\u30FB\u5F15\u304D\u7D99\u304E\u30FB\u5165\u51FA\u529B\u30FB\u6210\u679C\u7269) \u306F\u5168\u90E8\u3001\u8A08\u753B\u5168\u4F53\u306E\u8AAC\u660E\u3001\u89AA\u3068\u5165\u529B\u5143\u306E\u984C\u540D\u30FB\u72B6\u614B\u30FB\u5224\u65AD (\u6750\u6599\u3068\u9078\u629E\u80A2\u3064\u304D\u3002\u524D\u306E\u56DE\u7B54\u306E\u5C65\u6B74\u306F\u7701\u304F)\u30FB\u5BFE\u8C61\u306B\u3064\u306A\u304C\u308B\u51FA\u529B\u3092\u51FA\u3059\u3002\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F `--brief` \u306A\u3057\u3068\u540C\u3058\u306A\u306E\u3067\u3001\u305D\u306E\u307E\u307E `--context-token` \u306B\u4F7F\u3048\u308B\u3002`target.missingInputs` (\u4E0D\u8DB3\u3057\u3066\u3044\u308B\u5FC5\u9808\u306E\u5165\u529B) \u3068 `target.inputs[].sources` (\u3069\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u3069\u306E\u51FA\u529B\u304B\u30FB\u6210\u679C\u7269\u306E\u53C2\u7167) \u3092\u5148\u306B\u898B\u308B\u3002`omitted` \u306B\u7701\u3044\u305F\u4EF6\u6570\u304C\u51FA\u308B\u3002\u7701\u304B\u308C\u305F\u8AAC\u660E\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u304C\u4F5C\u696D\u306B\u95A2\u308F\u308A\u305D\u3046\u306A\u3089\u3001`--brief` \u306A\u3057\u3067\u8AAD\u3080\u304B\u3001\u305D\u306E\u30DC\u30C3\u30AF\u30B9\u306E `context` \u3092\u8AAD\u3080\n- **\u77ED\u3044\u5F62\u3067\u59CB\u3081\u308B\u524D\u63D0**: \u7BC4\u56F2 (scope) \u3092 1 \u9805\u76EE\u3067\u3082\u8A2D\u5B9A\u3057\u305F\u89AA\u30FB\u5165\u529B\u5143\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u8AAC\u660E\u306F\u3001\u77ED\u3044\u5F62\u3067\u306F\u7701\u304B\u308C\u308B (`omitted.descriptions` \u306B\u4EF6\u6570)\u3002\u7BC4\u56F2\u3092\u8A2D\u5B9A\u3059\u308B\u3068\u304D\u306F\u3001\u8AAC\u660E\u306B\u66F8\u3044\u305F\u5236\u7D04 (\u4E92\u63DB\u6027\u30FB\u7981\u6B62\u4E8B\u9805\u30FB\u671F\u9650\u306A\u3069) \u3092\u7BC4\u56F2\u3078\u5199\u3057\u7D42\u3048\u3066\u304B\u3089\u306B\u3059\u308B\u3002\u5199\u3057\u305F\u304B\u5206\u304B\u3089\u306A\u3044\u8A08\u753B\u30FB`omitted.descriptions` \u304C 0 \u3067\u306A\u3044\u8A08\u753B\u3067\u306F\u3001\u7740\u624B\u524D\u306B `--brief` \u306A\u3057\u3067 1 \u5EA6\u8AAD\u3080\u3002\u77ED\u3044\u5F62\u306F\u8ABF\u67FB\u306E\u51FA\u767A\u70B9\u3067\u3001\u305D\u308C\u3060\u3051\u8AAD\u3081\u3070\u5341\u5206\u3068\u3044\u3046\u4FDD\u8A3C\u3067\u306F\u306A\u3044\n- \u77ED\u3044\u5F62\u3067\u3082\u7528\u304C\u8DB3\u308A\u308B\u3088\u3046\u306B\u66F8\u304F: \u4F5C\u696D\u3092\u7E1B\u308B\u6761\u4EF6\u306F\u8AAC\u660E\u306E\u4E2D\u306B\u57CB\u3081\u305A\u306B scope (non-goals / acceptance / consult) \u306B\u66F8\u304F\u3002\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u306F\u6700\u65B0\u306E 1 \u4EF6\u3060\u3051\u304C\u6B8B\u308B\u306E\u3067\u3001\u672A\u89E3\u6C7A\u306E\u70B9\u30FB\u6CE8\u610F\u70B9\u30FB\u6B21\u306E\u624B\u9806\u3092\u6BCE\u56DE\u66F8\u304D\u76F4\u3057\u3066\u6301\u3061\u8D8A\u3059\u3002\u8ABF\u3079\u59CB\u3081\u308B\u5834\u6240\u306F\u3001\u5BFE\u8C61\u306E\u30DC\u30C3\u30AF\u30B9\u306E\u8AAC\u660E\u306B\u6B21\u306E\u5F62\u3067\u66F8\u304F (\u51FA\u767A\u70B9\u3067\u3042\u308A\u3001\u305D\u306E\u30D5\u30A1\u30A4\u30EB\u3060\u3051\u8AAD\u3081\u3070\u8DB3\u308A\u308B\u3068\u3044\u3046\u610F\u5473\u3067\u306F\u306A\u3044):\n `\u5909\u66F4\u5019\u88DC: <\u30D1\u30B9> \u2014 <\u8ABF\u3079\u59CB\u3081\u308B\u7406\u7531>` / `\u95A2\u9023\u691C\u67FB: <\u30D1\u30B9\u307E\u305F\u306F\u30B3\u30DE\u30F3\u30C9> \u2014 <\u78BA\u304B\u3081\u308B\u3053\u3068>`\n- \u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u901A\u3059\u305F\u3081\u306B guard \u3092\u7121\u52B9\u306B\u3057\u306A\u3044 (AI \u304C `guard off` \u3059\u308B\u306B\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u8981\u308B)\n- MCP \u3067\u306F `boxglow_context` \u3068\u3001\u5404\u30C4\u30FC\u30EB\u306E `contextToken` \u5F15\u6570\u3092\u4F7F\u3046 (\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u30C4\u30FC\u30EB\u306E\u7D50\u679C\u306E\u6700\u5F8C\u306E\u884C\u306B\u51FA\u308B)\n- \u300C\u4ED6\u306E Boxglow \u304C\u66F8\u304D\u8FBC\u307F\u4E2D (\u30ED\u30C3\u30AF\u4E2D)\u300D\u3068\u51FA\u3066\u3001\u5C11\u3057\u5F85\u3063\u3066\u3084\u308A\u76F4\u3057\u3066\u3082\u89E3\u3051\u306A\u3044\u3068\u304D\u306F\u3001`npx boxglow unlock` \u306E\u8868\u793A (\u6301\u3061\u4E3B\u3068\u5224\u5B9A) \u3092\u4EBA\u306B\u898B\u305B\u3066\u89E3\u9664\u3092\u983C\u3080\u3002\u81EA\u5206\u3067 `--actor human` \u3092\u4ED8\u3051\u305F\u308A\u3001\u30ED\u30C3\u30AF\u306E\u30D5\u30A9\u30EB\u30C0\u3092\u76F4\u63A5\u6D88\u3057\u305F\u308A\u3057\u306A\u3044\n\n### \u7740\u624B\u30FB\u5B8C\u4E86\u3001\u4ECA\u56DE\u306E\u7BC4\u56F2\u3001\u8A18\u9332\u306E\u9BAE\u5EA6\n\n- \u6700\u521D\u306B resume \u306E\u73FE\u6CC1 (\u6D3B\u52D5\u30FB\u5224\u65AD\u5F85\u3061\u30FB\u672A\u78BA\u8A8D\u56DE\u7B54) \u3092\u8AAD\u3080\u3002\u5019\u88DC\u306F\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2 / \u305D\u306E\u4ED6\u300D\u306E\u5404\u7D44\u3067\u300C\u7740\u624B\u3067\u304D\u308B / \u5165\u529B\u5F85\u3061\u300D\u306B\u5206\u304B\u308C\u3001\u4E0D\u8DB3\u3059\u308B\u5165\u529B\u540D\u304C\u51FA\u308B\u3002\u5165\u529B\u5F85\u3061\u3092\u3001\u6E96\u5099\u6E08\u307F\u3068\u53D6\u308A\u9055\u3048\u306A\u3044\u3002\n- \u30DC\u30C3\u30AF\u30B9\u306E\u4EFB\u610F4\u9805\u76EE\u3092 scope \u3067\u8A2D\u5B9A\u3059\u308B\u3002goal = \u4ECA\u56DE\u9054\u6210\u3059\u308B\u3053\u3068\u3001non-goals = \u4ECA\u56DE\u306F\u6271\u308F\u306A\u3044\u3053\u3068\u3001acceptance = \u5B8C\u4E86\u3068\u5224\u65AD\u3059\u308B\u6761\u4EF6\u3001consult = \u7BC4\u56F2\u3092\u5E83\u3052\u308B\u524D\u306B\u76F8\u8AC7\u3059\u308B\u6761\u4EF6\u3002context \u306E\u5148\u982D\u3067\u5BFE\u8C61\u3068\u89AA\u306E\u7BC4\u56F2\u3092\u8AAD\u307F\u3001\u7740\u624B\u524D\u3068\u5B8C\u4E86\u524D\u306B\u7167\u5408\u3059\u308B\u3002\u5B8C\u4E86\u6761\u4EF6\u306E\u672C\u6587\u3092AI\u304C\u6E80\u305F\u3057\u305F\u304B\u306F\u81EA\u52D5\u5224\u5B9A\u3057\u306A\u3044\u3002\n- scope B12 --goal "\u9054\u6210\u3059\u308B\u3053\u3068" --non-goals "\u4ECA\u56DE\u6271\u308F\u306A\u3044\u3053\u3068" --acceptance "\u78BA\u8A8D\u3059\u308B\u6761\u4EF6" --consult "\u76F8\u8AC7\u3059\u308B\u6761\u4EF6" \u3067\u8A2D\u5B9A\u3059\u308B\u3002scope B12 \u306F\u8868\u793A\u3060\u3051\u3002\u5404\u9805\u76EE\u306B none \u307E\u305F\u306F\u7A7A\u6587\u5B57\u3092\u6E21\u3059\u3068\u6D88\u3059\u3002\u753B\u9762\u3067\u306F\u8A73\u7D30\u30D1\u30CD\u30EB\u306E \u22EF \u2192\u300C\u4F5C\u696D\u7BC4\u56F2\u3092\u7DE8\u96C6\u300D\u3002\u8A18\u5165\u6E08\u307F\u306E\u9805\u76EE\u3060\u3051\u304C\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2\u300D\u306B\u51FA\u308B\u3002\n- focus B12 \u3067\u3053\u306E\u30DC\u30C3\u30AF\u30B9\u3068\u914D\u4E0B\u3092\u6B21\u5019\u88DC\u306E\u5148\u982D\u306B\u3059\u308B\u3002focus \u306F\u73FE\u5728\u306E\u5BFE\u8C61\u306E\u8868\u793A\u3001focus none \u306F\u89E3\u9664\u3002\u5BFE\u8C61\u5916\u3082\u5225\u306E\u7D44\u306B\u6B8B\u308B\u3002\u5019\u88DC\u306B\u51FA\u308B\u3053\u3068\u81EA\u4F53\u306F\u4F5C\u696D\u7BC4\u56F2\u306E\u62E1\u5927\u3084\u516C\u958B\u3092\u8A31\u53EF\u3057\u306A\u3044\u3002\n- start \u306E\u5FC5\u9808\u5165\u529B\u4E0D\u8DB3\u306F\u65E2\u5B9A\u3067\u8B66\u544A\u3059\u308B\u304C\u3001\u5F93\u6765\u3069\u304A\u308A\u958B\u59CB\u3067\u304D\u308B\u3002\u5148\u884C\u4F5C\u696D\u304C\u5FC5\u8981\u306A\u3089 --reason "\u5148\u306B\u884C\u3046\u7406\u7531" \u3092\u4ED8\u3051\u308B\u3002\u8B66\u544A\u306F\u51FA\u305A\u3001\u7406\u7531\u306F\u6D3B\u52D5\u3068\u30ED\u30B0\u306B\u6B8B\u308B\u3002done \u306E\u51FA\u529B\u6210\u679C\u7269\u306A\u3057\u3082\u65E2\u5B9A\u3067\u8B66\u544A\u306E\u307F\u3002\u65E2\u5B58\u306E\u51FA\u529B\u6210\u679C\u7269\u307E\u305F\u306F --artifact \u3092\u6570\u3048\u3001\u30DC\u30C3\u30AF\u30B9\u306E\u53C2\u8003\u8CC7\u6599\u306F\u6570\u3048\u306A\u3044\u3002\n- \u8A08\u753B\u3067\u9078\u3076\u5834\u5408\u3060\u3051 policy --start reject --done reject \u306B\u3059\u308B\u3002\u305D\u308C\u305E\u308C warn \u306B\u623B\u305B\u308B\u3002\u5165\u529B\u5F85\u3061\u306E\u62D2\u5426\u3067\u3082\u7406\u7531\u4ED8\u304D\u306E\u958B\u59CB\u306F\u53EF\u80FD\u3002\u6210\u679C\u7269\u306A\u3057\u306E\u62D2\u5426\u306F set --status white \u306B\u3082\u52B9\u304F\u3002\u4EBA\u306E\u64CD\u4F5C (\u753B\u9762 / --actor human) \u306F\u62D2\u5426\u3057\u306A\u3044\u3002AI\u304C\u62D2\u5426\u3092\u907F\u3051\u308B\u305F\u3081\u306B\u4EBA\u3092\u540D\u4E57\u3063\u305F\u308A\u3001\u8A2D\u5B9A\u3092\u5F31\u3081\u305F\u308A\u3057\u306A\u3044\u3002\n- guard \u6709\u52B9\u6642\u3001scope \u306E\u5909\u66F4\u3001focus \u306E\u8A2D\u5B9A\u30FB\u89E3\u9664\u3001policy \u306E\u5909\u66F4\u306B\u3082 --context-token \u304C\u5FC5\u8981\u3002scope/focus/policy \u3092\u5909\u3048\u308B\u3068\u65B0\u3057\u3044\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u3067\u3001\u6B21\u306E\u64CD\u4F5C\u306F\u305D\u308C\u3092\u4F7F\u3046\u3002\u8868\u793A\u3060\u3051\u306B\u306F\u4E0D\u8981\u3002\n- resume \u306F\u5B8C\u4E86\u6E08\u307F\u306E\u5F15\u304D\u7D99\u304E\u672C\u6587\u3092\u96A0\u3057\u3001\u4EF6\u6570\u3060\u3051\u793A\u3059\u3002\u5C65\u6B74\u304C\u5FC5\u8981\u306A\u5834\u5408\u3060\u3051 resume --include-completed \u3067\u5C55\u958B\u3059\u308B (context / show \u3067\u306F\u500B\u5225\u306B\u8AAD\u3081\u308B)\u3002\n- \u8AAC\u660E\u306F descriptionUpdatedAt\u3001\u72B6\u614B\u306F statusChangedAt\u3001\u5F15\u304D\u7D99\u304E\u306F\u65E2\u5B58\u306E at \u3067\u65E5\u6642\u3092\u78BA\u8A8D\u3067\u304D\u308B\u3002\u72B6\u614B\u5909\u66F4\u3088\u308A\u53E4\u3044\u30E1\u30E2\u306F\u305D\u306E\u65E8\u3092\u793A\u3059\u3002\u65E7\u8A18\u9332\u306E\u4E0D\u660E\u306A\u65E5\u6642\u306F\u88DC\u308F\u306A\u3044\u3002\u300C\u65B0\u3057\u3044\u8A18\u9332\u300D\u306F\u771F\u507D\u306E\u4FDD\u8A3C\u3067\u306F\u306A\u3044\u3002\n- Done \u3067\u53E4\u3044\u72B6\u6CC1\u8AAC\u660E\u304C\u6B8B\u308B\u53EF\u80FD\u6027\u304C\u3042\u308B\u5834\u5408\u3001CLI\u30FB\u8A73\u7D30\u30D1\u30CD\u30EB\u30FBresume \u306B\u898B\u76F4\u3057\u6848\u5185\u304C\u51FA\u308B\u3002\u672C\u6587\u306F\u81EA\u52D5\u3067\u6D88\u3055\u306A\u3044\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089 set B12 --note "\u73FE\u5728\u306E\u8AAC\u660E" (none \u3067\u6D88\u53BB) \u307E\u305F\u306F\u753B\u9762\u306E Notes \u3067\u76F4\u3059\u3002\n- MCP \u306F boxglow_scope / boxglow_focus / boxglow_policy\u3001boxglow_start \u306E reason\u3001boxglow_resume \u306E includeCompleted \u3092\u4F7F\u3046\u3002\u5909\u66F4\u306B\u306F contextToken \u3092\u4ED8\u3051\u308B\u3002\n- \u30C7\u30FC\u30BF\u5F62\u5F0F\u306F v5 \u306E\u307E\u307E\u3067\u5168\u9805\u76EE\u306F\u4EFB\u610F\u3002\u65E7\u7248 (0.4.2\u4EE5\u524D) \u306F\u65B0\u3057\u3044\u898F\u5247\u3092\u5B9F\u884C\u305B\u305A\u3001\u4FDD\u5B58\u6642\u306B\u8A08\u753B\u306E workflowPolicy / focusBlockId \u3092\u843D\u3068\u3059\u3002\u3053\u308C\u3089\u3092\u4F7F\u3046\u8A08\u753B\u306E\u66F8\u304D\u624B (CLI\u30FBserve\u30FB\u753B\u9762\u30FB\u62E1\u5F35\u5185\u306E\u30A2\u30D7\u30EA) \u306F\u5BFE\u5FDC\u3057\u305F\u30D3\u30EB\u30C9\u306B\u63C3\u3048\u308B\u3002\u7248\u756A\u53F7\u3060\u3051\u3067\u306A\u304F\u30D3\u30EB\u30C9\u65E5\u6642\u3082\u78BA\u8A8D\u3059\u308B\u3002\n\n---\n';
|
|
5691
|
+
|
|
5692
|
+
// docs/AGENTS_SNIPPET.en.md
|
|
5693
|
+
var AGENTS_SNIPPET_en_default = '# Instructions for agents using Boxglow (paste into AGENTS.md / CLAUDE.md)\n\nPaste the part between the "---" lines below, as is, into your repository\'s AGENTS.md (Codex) or CLAUDE.md (Claude Code).\nPut boxglow.json at the repository root (`npx boxglow init --name "<project name>"`).\n\n---\n\n## Boxglow (shared plan and progress)\n\n`boxglow.json` is the source of truth for this repository\'s plan and progress. Humans watch this file on the Boxglow screen.\nDo not edit the file directly; always update it with `npx boxglow` commands (this keeps the wiring rules and the history intact).\n\nA block = "a task that turns inputs into an output (deliverable)". It is done (WhiteBox) once the output\'s deliverable is settled.\nStatus: BlackBox (only the output is decided) \u2192 GrayBox (being broken down or worked on) \u2192 WhiteBox (done).\n\n### What to do every time you work\n\n1. **Read first**: read `npx boxglow resume` (or `npx boxglow status --brief`) for work in progress, pending decisions, unread answers, next candidates, and handoffs for unfinished boxes. To continue a previous session, start from the handoff notes shown there. Check the inputs and outputs of your box with `npx boxglow show <block>`\n2. **Record the start**: when you take on a box, run `npx boxglow start <block> --note "<what you will do>"`. Work on at most 1-2 boxes at a time.\n On a plan with the context guard on, first read `npx boxglow context <block>` and pass the `contextToken` (context token) from its output:\n `npx boxglow start <block> --note "<what you will do>" --context-token <context token>` (see "Context token and handoff" below)\n3. **Split**: when you cannot see how to produce the output with a single box, place smaller boxes inside it with `npx boxglow split <block> --spec \'<JSON>\'`. Always decide the output (deliverable) of each box. Inputs can be omitted if unknown (they automatically become inputs of the level above)\n4. **Record completion**: when the output\'s deliverable exists, run `npx boxglow done <block> --artifact "<name>=<path in the repository>"`.\n With the guard on, add `--context-token <context token>` (if your previous command printed "New context token: <token>", use that one).\n An unchanged file already committed to HEAD is recorded as "commit + path + content hash" (nothing is uploaded). Uncommitted or untracked files remain local file references, and `npx boxglow check` turns them into Git references once they are committed; do not create a commit solely to satisfy Boxglow. Use full `npx boxglow status` when you need the entire tree. For PRs and external material, a URL is fine.\n To attach a deliverable ahead of completion, use `npx boxglow artifact <block> <path>`. Record progress along the way with `npx boxglow set <block> --progress 60`\n5. **When a human decision is needed**: `npx boxglow ask <block> "<question>" --options "A|B"`. Do not proceed with that box until it is answered; move on to another box. A human\'s answer appears under "Answered" in `status`. After reading it, acknowledge it with `npx boxglow ack <block>` (`start` / `done` / `set` etc. on that box also acknowledge it automatically). Until you acknowledge it, it stays on the human\'s screen as "Not read by the AI yet" and the human can still revise the answer. If the answer contains a question back to you, record your own answer too with `ask` and `answer --by <yourself>`\n6. **When you are stuck**: `npx boxglow blocked <block> --note "<what is in the way>"`\n7. **Check deliverables**: after moving or renaming files, run `npx boxglow check` (it detects the move and updates the path; if the file cannot be found, it is flagged)\n8. **Before an interruption or a handoff**: before the session ends, the context is compacted, or you hand over to another AI or a person, save a handoff note with `npx boxglow checkpoint <block> --note "Findings; next steps; unresolved questions"` (with the guard on, add `--context-token <context token>`). It stays in the plan even when the chat history is gone, and the next session reads it with `resume` and `context`\n9. **Report**: at the end of your work, summarize `npx boxglow status --brief` and report it\n\n### Structural rules\n\n- The top level contains only the "input node", the "final deliverable node" and "project boxes". Tasks go inside a project box (the default parent of `add` is the first project box)\n- There is no limit on depth. `split` can be used at any level\n- A box that could be reused in another project (e.g. converting input images to monochrome) can be turned into a template with `npx boxglow export-block <block> --out <name>.boxglow-block.json` and\n inserted with `npx boxglow import-block <path> --parent <block>`. You may share templates by putting them in the repository\'s `boxglow-blocks/`\n\nWhen there are many top-level inputs, create a group with `npx boxglow group "<group name>"` and sort inputs into it with `npx boxglow group-set <input name> <group name>` (e.g. PCIe spec / DDR spec).\n\n### JSON format for split\n\n```json\n{\n "blocks": [\n { "title": "Design", "description": "What to do", "inputs": ["Spec"], "outputs": ["Design doc"] },\n { "title": "Build", "inputs": ["Design doc"], "outputs": ["Code"] }\n ],\n "connections": [\n { "from": "parent.Spec", "to": "Design.Spec" },\n { "from": "Design.Design doc", "to": "Build.Design doc" },\n { "from": "Build.Code", "to": "parent.Output" }\n ]\n}\n```\n\n`parent.<name>` is an input / output of the box being split. `<block>` is a short ID (such as B12; shown in status) or a title.\nA box with no children has a single output (write just one entry in outputs). Make the title a short phrase that tells "what is made".\nRecord dates and hours with `npx boxglow set <block> --due 2026-10-15 --start 2026-10-01 --estimate 8 --hours 3.5`.\nGive each box a kind of work (category): `--category design` / build / verify / evaluate / study / research / ui / improve / fix / docs / ops / other (other: when unsure). Set it at add time; to change it, use `npx boxglow set <block> --category verify`.\nThe receiving side of a connection can be just a title (`npx boxglow connect "A.Design doc" "B"` creates an input "Design doc" on B and connects it. In split\'s connections, `to: "B"` also works). A connected input takes the name of the source output and cannot be renamed on the input side (to change it, rename the source output; everything connected to it changes with it). To add inputs / outputs to an existing box or rename them, use `npx boxglow port <block> --in <name> --out <name> --rename <old>=<new>` (`project` = the first project box. This also renames the final deliverable). Remove a wire with `npx boxglow disconnect <title.output name> <title.input name>`, and remove a box itself with `npx boxglow remove <block>` (`--force` if it has boxes inside). The MCP tools (`boxglow_status` / `boxglow_start` / `boxglow_done` / `boxglow_ask` etc.; available after registration in your agent; `.mcp.json` is the Claude Code configuration) are another entry point to the same commands, and either way updates the same file. Export the plan as a document with `npx boxglow export --out docs/ROADMAP.md`.\nIn a new project, first create 3-7 top-level items from the direction (the README or the request), confirm them with a human via `ask`, and then `split` only the item you are about to start (leave later stages coarse). Write each `ask` question so that it can be decided on its own, and put the premises, comparison and impact in `--context` (do not point outside the question, as in "Is this OK?"). When choosing one of several candidates, even if you decide by yourself, record it with `ask ... --options "A|B|C"` and `answer --by <yourself>` so that the candidates you did not choose are kept. To change direction, use `reopen <block> --note <reason>`. Everything works with the CLI alone (the screen is for humans to watch). If no human is in the loop, the AI records its decisions with `answer --by <yourself>` and proceeds.\nName each output after a "concrete deliverable" (e.g. `Design doc docs/design.md`, `PR #12`, `Public URL`, `Test results (vitest, 53 tests)`). Avoid abstract names such as "feature set" or "findings", and always attach a file, URL or commit with --artifact when you run done.\nMake sure every box\'s "input \u2192 output" is connected to something (an unconnected box is a hole in the plan. Unconnected inputs are raised to the top-level input node automatically).\nIf several people edit the same boxglow.json, each of them runs `npx boxglow git-setup` once in their own clone (changes are merged automatically per box, and changes to different boxes do not conflict).\nTo manage several repositories with one plan, put boxglow.json in the folder above them and set the environment variable BOXGLOW_FILE to its location in each repository (one project box per repository: `npx boxglow project "name" --repo <path>`).\nInputs are required by default. On the screen, mark an input as "Optional" if work can start without it. "Next candidates" in status lists boxes whose required inputs are ready first (each is marked "ready to start" or "waiting for required inputs").\nDo not write descriptions for inputs (the description of the connected output is used). Write the format and constraints in the output\'s description.\n\n\n### Context token and handoff\n\nThe context guard keeps an AI from continuing work on outdated instructions. `setup-agent` turns it on (on other plans, run `npx boxglow guard on`).\nWhile it is on, an AI\'s `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` need `--context-token <context token>`. Commands run by a person (`--actor human`) do not.\n\n```\nnpx boxglow context B12 # read the decisions (including acknowledged answers), input/output contracts and handoff notes. contextToken in the output is the context token\nnpx boxglow start B12 --note "<what you will do>" --context-token <context token>\nnpx boxglow ask B12 "<question>" --options "A|B" --context-token <context token>\n# -> last line of the output: New context token: <token>\nnpx boxglow done B12 --artifact "<name>=<path>" --context-token <new context token>\n```\n\n- **The context token changes (you must reread)** when a person answers a decision or revises an answer, a person changes the instructions (title, description, input/output contracts), or another AI or a person updates the handoff note. The same applies when this happens on a parent box or an input provider. The old context token is rejected: reread `context`, take the change into account in your work, then retry\n- **The context token does not change** on `start` / `blocked` / `review` / `leave` / `ack`, on changes to progress, status, dates or category, when `check` verifies deliverables, or when boxes are moved on the screen. Keep using the same context token\n- **You can update it yourself**: when your own `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` etc. changes the context, the last line of its output is "New context token: <token>". You already know what you changed, so use it for the next command without rereading. `ask` / `decision` / `answer` / `reopen` run without a context token, but they print the new context token only when you pass the latest one\n- **Read the short form first**: `npx boxglow context <block> --brief` prints everything about the box itself (description, scope, decisions, handoff, inputs/outputs, deliverables) plus the plan description and, for parents and input providers, title, status, decisions (with their context and options; the history of earlier answers is left out) and the outputs that feed the box. The context token is the same as without `--brief`, so pass it to `--context-token` as is. Look at `target.missingInputs` (required inputs that are not ready) and `target.inputs[].sources` (which output of which box, with references to its deliverables) first. `omitted` counts what was left out. If an omitted description, contract or handoff may matter to your work, read without `--brief`, or read `context` of that box\n- **Precondition for starting from the short form**: the description of a parent or input provider that has any scope field set is left out of the short form (counted in `omitted.descriptions`). Set a scope only after moving the constraints written in the description (compatibility, prohibitions, deadlines) into it. On a plan where you cannot tell, or when `omitted.descriptions` is not 0, read once without `--brief` before you start. The short form is a starting point, not a guarantee that nothing else matters\n- Write so that the short form is enough: put the conditions that bind the work in the scope (non-goals / acceptance / consult) instead of burying them in a description. Only the latest handoff note is kept, so carry the open points, cautions and next steps forward each time you write one. Say where to start looking in the description of the box, in this form (a starting point, not a claim that those files are all there is to read):\n `Change candidates: <path> \u2014 <why to start there>` / `Related checks: <path or command> \u2014 <what it verifies>`\n- Do not turn the guard off to get past an old context token (an AI needs the latest context token to run `guard off`)\n- With MCP, use `boxglow_context` and the `contextToken` argument of each tool (the new context token is the last line of the tool result)\n- If "Another Boxglow writer holds the file lock" persists after waiting and retrying, show the output of `npx boxglow unlock` (owner and verdict) to a person and ask them to remove it. Never add `--actor human` yourself or delete the lock folder directly\n\n### Readiness, current scope, and fresh resume information\n\n- Read current activity, pending decisions, and unread answers first. Candidates are grouped by current scope / other candidates, then ready / waiting for inputs, with missing input names. A waiting candidate is not ready.\n- Four optional scope fields are available: goal, non-goals, acceptance, and consult (when to consult before expanding scope). Read the target and parent scopes at the start of context before starting and finishing. Textual acceptance criteria are not automatically evaluated.\n- Use scope B12 --goal "Goal" --non-goals "Excluded" --acceptance "Checks" --consult "When to ask". scope B12 only reads; none or an empty string clears a field. In the inspector, use \u22EF \u2192 Edit work scope. Only populated fields appear in the collapsed scope section.\n- focus B12 prioritizes that box and its descendants; focus reads the selection, and focus none clears it. Other candidates remain in a separate group. Being listed does not authorize expanding scope or publishing.\n- Missing required inputs warn on start by default but still allow the start. Supply --reason "Why starting early is appropriate" to proceed without a warning and save the reason in both activity and the log. Artifact-free done also remains warning-only by default. Existing output artifacts or --artifact count; reference material on the box does not.\n- Only enable strict checks when selected for the plan: policy --start reject --done reject. Each can return to warn. A recorded start reason also overrides reject. The artifact requirement also applies to set --status white. Human operations (UI / --actor human) are never rejected. Do not impersonate a human or weaken policy to bypass rejection.\n- With guard enabled, scope writes, focus selection/clearing, and policy writes require --context-token. These changes print a new token: use it next. Read-only forms need no token.\n- resume hides completed handoff bodies and shows their count. Request resume --include-completed only when history is needed; context / show can still read individual notes.\n- descriptionUpdatedAt dates description changes; statusChangedAt dates status changes; handoffs use their existing at field. Notes predating a status change are marked. Unknown legacy dates stay unknown, and recency does not establish truth.\n- After Done, CLI, inspector, and resume flag descriptions that may still describe an earlier state. Text is never rewritten automatically. Review it, then update with set B12 --note "Current description" (none clears it) or Notes in the UI.\n- MCP: boxglow_scope / boxglow_focus / boxglow_policy, reason on boxglow_start, and includeCompleted on boxglow_resume. Pass contextToken for writes.\n- All data fields are optional; schema v5 is unchanged. Old versions (0.4.2 and earlier) do not enforce these checks and drop workflowPolicy / focusBlockId when saving. Align all writers (CLI, serve, web app, bundled extension app) to a supporting build before using these settings. Check build time as well as version.\n\n---\n';
|
|
5694
|
+
|
|
5695
|
+
// .claude/skills/boxglow/SKILL.md
|
|
5696
|
+
var SKILL_default = '---\nname: boxglow\ndescription: Boxglow (boxglow.json) \u3067\u8A08\u753B\u3068\u9032\u6357\u3092\u4EBA\u9593\u3068\u5171\u6709\u3059\u308B\u3002\u4F5C\u696D\u306E\u958B\u59CB\u30FB\u5206\u89E3\u30FB\u5B8C\u4E86\u30FB\u5224\u65AD\u5F85\u3061\u3092 npx boxglow \u3067\u8A18\u9332\u3059\u308B\u3002\u300C\u9032\u6357\u3092 Boxglow \u306B\u8A18\u9332\u3057\u3066\u300D\u300Cboxglow \u3067\u72B6\u6CC1\u3092\u898B\u3066\u300D\u300C/boxglow\u300D\u3067\u4F7F\u7528\u3002\n---\n\n# Boxglow \u3067\u8A08\u753B\u3068\u9032\u6357\u3092\u5171\u6709\u3059\u308B\n\n\u30EA\u30DD\u30B8\u30C8\u30EA\u76F4\u4E0B\u306E `boxglow.json` \u304C\u8A08\u753B\u306E\u6B63\u672C\u3002\u4EBA\u9593\u306F Boxglow \u306E\u753B\u9762 (https://boxglow.pages.dev/ \u3067\u300Cboxglow.json \u3092\u958B\u304F\u300D) \u3067\u898B\u3066\u3044\u308B\u3002\n\u76F4\u63A5\u7DE8\u96C6\u305B\u305A `npx boxglow` \u3067\u66F4\u65B0\u3059\u308B\u3002\n\n## \u6700\u521D\u306E\u8A08\u753B\u3065\u304F\u308A (\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u3068\u304D)\n\n1. \u65B9\u91DD (README\u3001\u4F9D\u983C\u6587\u3001\u8981\u4EF6\u30E1\u30E2) \u3092\u8AAD\u307F\u3001`npx boxglow init --name "<\u540D\u524D>"` \u306E\u3042\u3068\u3001\u6700\u4E0A\u4F4D\u306B\u5927\u9805\u76EE (3\u301C7 \u500B) \u3092 `add` \u3067\u7F6E\u304F\u3002\n \u5404\u5927\u9805\u76EE\u306B\u306F\u300C\u5165\u529B (\u4F55\u3092\u4F7F\u3046\u304B)\u300D\u3068\u300C\u51FA\u529B (\u5177\u4F53\u7684\u306A\u6210\u679C\u7269: \u30D5\u30A1\u30A4\u30EB\u30FBURL\u30FBPR)\u300D\u3092\u5FC5\u305A\u66F8\u304D\u3001\u6700\u7D42\u6210\u679C\u7269\u307E\u3067\u7DDA\u3067\u3064\u306A\u3050 (`connect`)\n2. \u4F5C\u3063\u305F\u3089 `npx boxglow ask <\u6700\u521D\u306E\u5927\u9805\u76EE> "\u3053\u306E\u5927\u9805\u76EE\u306E\u5206\u3051\u65B9\u3067\u3088\u3044\u3067\u3059\u304B?" --options "\u3088\u3044|\u76F4\u3057\u305F\u3044"` \u3067\u4EBA\u306E\u78BA\u8A8D\u3092\u53D6\u308B\u3002\n \u4EBA\u306F\u753B\u9762\u3067\u3082\u3001`npx boxglow answer <block> "<\u56DE\u7B54>"` \u3067\u3082\u3001\u30C1\u30E3\u30C3\u30C8\u3067\u3082\u7B54\u3048\u3089\u308C\u308B\u3002\u30C1\u30E3\u30C3\u30C8\u3067\u7B54\u3048\u304C\u8FD4\u3063\u305F\u3089 AI \u304C `answer --by human` \u3067\u8A18\u9332\u3059\u308B\u3002\n \u4EBA\u304C\u753B\u9762\u3092\u4F7F\u308F\u306A\u3044\u904B\u7528 (AI \u3060\u3051\u3067\u9032\u3081\u308B\u6307\u793A\u304C\u3042\u308B\u3068\u304D) \u306F\u3001AI \u81EA\u8EAB\u304C `answer --by claude-code "<\u5224\u65AD\u3068\u7406\u7531>"` \u3067\u8A18\u9332\u3057\u3066\u5148\u3078\u9032\u3080\u3002\u5224\u65AD\u306E\u8A18\u9332\u3092\u6B8B\u3059\u3053\u3068\u304C\u76EE\u7684\u3067\u3001\u753B\u9762\u306F\u5FC5\u9808\u3067\u306F\u306A\u3044\n3. \u7740\u624B\u3059\u308B\u5927\u9805\u76EE\u3060\u3051\u3092 `split` \u3067\u5206\u89E3\u3059\u308B (\u5148\u306E\u6BB5\u968E\u306F\u7C97\u3044\u307E\u307E\u3002\u9032\u3081\u306A\u304C\u3089\u7D30\u304B\u304F\u3059\u308B)\n\n## \u624B\u9806\n\n1. `npx boxglow resume` (\u307E\u305F\u306F `npx boxglow status --brief`) \u3092\u8AAD\u3080\u3002\u4F5C\u696D\u4E2D\u306E\u30DC\u30C3\u30AF\u30B9\u3001\u5224\u65AD\u5F85\u3061\u3001AI \u672A\u78BA\u8A8D\u306E\u56DE\u7B54\u3001\u6B21\u306E\u5019\u88DC\u3001\u672A\u5B8C\u4E86\u306E\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u628A\u63E1\u3059\u308B\u3002\u5168\u968E\u5C64\u304C\u8981\u308B\u3068\u304D\u306F `npx boxglow status`\n2. \u62C5\u5F53\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u3092\u6C7A\u3081\u305F\u3089 `npx boxglow show <block>` \u3067\u5165\u51FA\u529B\u3092\u78BA\u8A8D\u3057\u3001`npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>"`\u3002\n \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u304C\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001\u5148\u306B `npx boxglow context <block>` \u3092\u8AAD\u307F\u3001\u51FA\u529B\u306E `contextToken` (\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3) \u3092 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3067\u4ED8\u3051\u308B\n3. \u5927\u304D\u3044\u30DC\u30C3\u30AF\u30B9\u306F `npx boxglow split <block> --spec \'<JSON>\'` \u3067\u5206\u89E3\u3059\u308B (\u5F62\u5F0F\u306F `npx boxglow help`)\u3002\u5404\u30DC\u30C3\u30AF\u30B9\u306E\u51FA\u529B\u3092\u5FC5\u305A\u6C7A\u3081\u308B\n4. \u6210\u679C\u7269\u304C\u3067\u304D\u305F\u3089 `npx boxglow done <block> --artifact "<\u540D\u524D>=<URL \u307E\u305F\u306F\u30D1\u30B9>"` (guard \u304C\u6709\u52B9\u306A\u3089 `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u3092\u4ED8\u3051\u308B\u3002\u76F4\u524D\u306E\u81EA\u5206\u306E\u64CD\u4F5C\u306E\u51FA\u529B\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u3066\u3044\u308C\u3070\u3001\u305D\u308C\u3092\u4F7F\u3046)\n5. \u4EBA\u9593\u306E\u5224\u65AD\u304C\u8981\u308B\u3068\u304D\u306F `npx boxglow ask <block> "<\u8CEA\u554F>" --options "A|B"` \u3057\u3066\u4ED6\u306E\u30DC\u30C3\u30AF\u30B9\u3078\u79FB\u308B\n6. \u8A70\u307E\u3063\u305F\u3089 `npx boxglow blocked <block> --note "<\u56F0\u3063\u3066\u3044\u308B\u3053\u3068>"`\n7. \u4E2D\u65AD\u30FB\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u5727\u7E2E\u30FB\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B `npx boxglow checkpoint <block> --note "\u5206\u304B\u3063\u305F\u3053\u3068; \u6B21\u306B\u3059\u308B\u3053\u3068; \u672A\u89E3\u6C7A\u306E\u3053\u3068"` \u3067\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u6B8B\u3059\n8. \u6700\u5F8C\u306B `npx boxglow status --brief` \u3092\u8981\u7D04\u3057\u3066\u5831\u544A\u3059\u308B\n\n## \u7D04\u675F\n\n- `ask` \u306E\u8CEA\u554F\u306F\u3001\u305D\u308C\u3060\u3051\u3067\u5224\u65AD\u3067\u304D\u308B\u3088\u3046\u306B\u66F8\u304F\u3002\u300C\u3053\u308C\u3067\u3088\u3044\u3067\u3059\u304B\u300D\u300C\u4E0A\u8A18\u306E\u6848\u300D\u306E\u3088\u3046\u306B\u5916\u3092\u6307\u3055\u305A\u3001\u524D\u63D0\u30FB\u6BD4\u8F03\u30FB\u5F71\u97FF\u3092 `--context` \u306B\u66F8\u304F (\u4EBA\u306F\u4E00\u89A7\u306E\u4E2D\u306E\u8CEA\u554F\u3060\u3051\u3092\u8AAD\u3093\u3067\u7B54\u3048\u308B)\n- \u5019\u88DC\u304B\u3089 1 \u3064\u3092\u9078\u3076\u5834\u9762 (\u65B9\u5F0F\u30FB\u30E9\u30A4\u30D6\u30E9\u30EA\u30FB\u8A2D\u8A08\u306E\u9078\u629E) \u3067\u306F\u3001AI \u304C\u81EA\u5206\u3067\u6C7A\u3081\u308B\u3068\u304D\u3082 `ask <block> "<\u554F\u3044>" --options "A|B|C"` \u2192 `answer --by claude-code "<\u9078\u3093\u3060\u3082\u306E>\u3002\u7406\u7531: ..."` \u306E\u5F62\u3067\u8A18\u9332\u3057\u3001\u9078\u3070\u306A\u304B\u3063\u305F\u5019\u88DC\u3092\u30DC\u30C3\u30AF\u30B9\u306B\u6B8B\u3059 (\u65B9\u91DD\u8EE2\u63DB\u306E\u3068\u304D\u306B\u623B\u308C\u308B)\n- \u65B9\u91DD\u8EE2\u63DB\u306F `reopen <block> --note "<\u7406\u7531>"` \u3067\u524D\u306E\u7B54\u3048\u3092\u5C65\u6B74\u306B\u6B8B\u3057\u3066\u9078\u3073\u76F4\u3059 (\u5019\u88DC\u306F\u6D88\u3055\u306A\u3044)\n\n- \u540C\u6642\u306B\u4F5C\u696D\u4E2D\u306B\u3059\u308B\u30DC\u30C3\u30AF\u30B9\u306F 1\u301C2 \u500B\u307E\u3067\n- \u6210\u679C\u7269 (artifact) \u306F\u5F8C\u304B\u3089\u4EBA\u304C\u958B\u3051\u308B\u3082\u306E (\u30B3\u30DF\u30C3\u30C8\u3001PR\u3001\u30D5\u30A1\u30A4\u30EB\u306E\u30D1\u30B9) \u306B\u3059\u308B\n- \u51FA\u529B\u306F\u5177\u4F53\u7684\u306A\u6210\u679C\u7269\u306E\u540D\u524D\u306B\u3059\u308B (\u30D5\u30A1\u30A4\u30EB\u30FBPR\u30FBURL\u30FB\u30C6\u30B9\u30C8\u7D50\u679C)\u3002\u62BD\u8C61\u7684\u306A\u540D\u524D (\u6A5F\u80FD\u4E00\u5F0F\u3001\u6240\u898B) \u306F\u907F\u3051\u308B\n- \u7D50\u7DDA\u306E\u53D7\u3051\u5074\u306F\u984C\u540D\u3060\u3051\u3067\u3088\u3044 (`connect "A.\u8A2D\u8A08\u66F8" "B"` \u3067 B \u306B\u5165\u529B\u300C\u8A2D\u8A08\u66F8\u300D\u304C\u4F5C\u3089\u308C\u308B)\u3002\u5165\u529B\u540D\u306F\u4F9B\u7D66\u5143\u306E\u51FA\u529B\u540D\u3067\u6C7A\u307E\u308A\u3001\u5165\u529B\u5074\u3067\u306F\u5909\u3048\u306A\u3044\n- \u30DC\u30C3\u30AF\u30B9\u306F\u5FC5\u305A\u300C\u5165\u529B \u2192 \u51FA\u529B\u300D\u3067\u3064\u306A\u3050\u3002\u8DB3\u308A\u306A\u3044\u5165\u51FA\u529B\u306F `port`\u3001\u5916\u3059\u306E\u306F `disconnect`\u3001\u30DC\u30C3\u30AF\u30B9\u305D\u306E\u3082\u306E\u3092\u6D88\u3059\u306E\u306F `remove`\u3002\u6587\u66F8\u5316\u306F `export --out docs/ROADMAP.md`\n- \u30DC\u30C3\u30AF\u30B9\u3092 add \u3059\u308B\u3068\u304D\u306F\u4ED5\u4E8B\u306E\u7A2E\u985E\u3092 `--category` \u3067\u4ED8\u3051\u308B (design \u8A2D\u8A08 / build \u5B9F\u88C5 / verify \u691C\u8A3C / evaluate \u8A55\u4FA1 / study \u691C\u8A0E / research \u8ABF\u67FB / ui \u30C7\u30B6\u30A4\u30F3 / improve \u6539\u5584 / fix \u8AB2\u984C\u89E3\u6C7A / docs \u6587\u66F8 / ops \u904B\u7528 / other \u305D\u306E\u4ED6)\u3002\u753B\u9762\u3067\u306F\u8272\u306E\u5E2F\u3068\u672D\u306B\u306A\u308B\n- boxglow.json \u304C\u307E\u3060\u7121\u3051\u308C\u3070 `npx boxglow init --name "<\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D>"` \u3067\u4F5C\u308A\u3001\u6700\u7D42\u6210\u679C\u7269\u3068\u6700\u4E0A\u4F4D\u306E\u30DC\u30C3\u30AF\u30B9\u3092 `add` \u3067\u7F6E\u304F\n\n## \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3068\u5F15\u304D\u7D99\u304E\n\n\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42 (guard) \u306F\u3001AI \u304C\u53E4\u3044\u6307\u793A\u306E\u307E\u307E\u4F5C\u696D\u3092\u9032\u3081\u308B\u306E\u3092\u9632\u3050\u4ED5\u7D44\u307F\u3002`setup-agent` \u304C\u6709\u52B9\u306B\u3059\u308B (\u305D\u308C\u4EE5\u5916\u306E\u8A08\u753B\u3067\u306F `npx boxglow guard on`)\u3002\n\u6709\u52B9\u306A\u8A08\u753B\u3067\u306F\u3001AI \u306E `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` \u306B `--context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>` \u304C\u8981\u308B\u3002\u4EBA\u306E\u64CD\u4F5C (`--actor human`) \u306B\u306F\u8981\u6C42\u3055\u308C\u306A\u3044\u3002\n\n```\nnpx boxglow context B12 # \u5224\u65AD (\u5F15\u304D\u53D6\u308A\u6E08\u307F\u306E\u56DE\u7B54\u3092\u542B\u3080)\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u8AAD\u3080\u3002\u51FA\u529B\u306E contextToken \u304C\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\nnpx boxglow start B12 --note "<\u4F55\u3092\u3059\u308B\u304B>" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\nnpx boxglow ask B12 "<\u8CEA\u554F>" --options "A|B" --context-token <\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n# \u2192 \u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C: \u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\nnpx boxglow done B12 --artifact "<\u540D\u524D>=<\u30D1\u30B9>" --context-token <\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3>\n```\n\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u308B\u5834\u5408 (\u8AAD\u307F\u76F4\u3057\u304C\u8981\u308B)**: \u4EBA\u304C\u56DE\u7B54\u3057\u305F\u30FB\u56DE\u7B54\u3092\u76F4\u3057\u305F\u3001\u4EBA\u304C\u6307\u793A (\u984C\u540D\u30FB\u8AAC\u660E\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6) \u3092\u5909\u3048\u305F\u3001\u4ED6\u306E AI \u3084\u4EBA\u304C\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u3092\u66F4\u65B0\u3057\u305F\u3002\u89AA\u3084\u5165\u529B\u5143\u306E\u30DC\u30C3\u30AF\u30B9\u3067\u8D77\u304D\u305F\u5834\u5408\u3082\u540C\u3058\u3002\u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u62D2\u5426\u3055\u308C\u308B\u306E\u3067\u3001`context` \u3092\u8AAD\u307F\u76F4\u3057\u3001\u5909\u308F\u3063\u305F\u5185\u5BB9\u3092\u4F5C\u696D\u306B\u53CD\u6620\u3057\u3066\u304B\u3089\u3084\u308A\u76F4\u3059\n- **\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u5909\u308F\u3089\u306A\u3044\u5834\u5408**: `start` / `blocked` / `review` / `leave` / `ack`\u3001\u9032\u6357\u30FB\u72B6\u614B\u30FB\u671F\u65E5\u30FB\u30AB\u30C6\u30B4\u30EA\u306E\u5909\u66F4\u3001`check` \u306B\u3088\u308B\u6210\u679C\u7269\u306E\u78BA\u8A8D\u3001\u753B\u9762\u3067\u306E\u914D\u7F6E\u306E\u5909\u66F4\u3002\u540C\u3058\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u3044\u7D9A\u3051\u3089\u308C\u308B\n- **\u81EA\u5206\u3067\u66F4\u65B0\u3067\u304D\u308B\u5834\u5408**: \u81EA\u5206\u306E `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` \u306A\u3069\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5909\u308F\u3063\u305F\u3068\u304D\u306F\u3001\u305D\u306E\u51FA\u529B\u306E\u6700\u5F8C\u306E\u884C\u306B\u300C\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3: <token>\u300D\u304C\u51FA\u308B\u3002\u8AAD\u307F\u76F4\u3055\u305A\u306B\u6B21\u306E\u64CD\u4F5C\u3078\u4F7F\u3046\u3002`ask` / `decision` / `answer` / `reopen` \u306F\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u7121\u3057\u3067\u3082\u5B9F\u884C\u3067\u304D\u308B\u304C\u3001\u65B0\u3057\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u4ED8\u3051\u305F\u3068\u304D\u3060\u3051\n- \u53E4\u3044\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u3092\u901A\u3059\u305F\u3081\u306B guard \u3092\u7121\u52B9\u306B\u3057\u306A\u3044 (AI \u304C `guard off` \u3059\u308B\u306B\u306F\u6700\u65B0\u306E\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u304C\u8981\u308B)\n- \u518D\u958B\u3059\u308B\u3068\u304D\u306F `npx boxglow resume` \u3092\u8AAD\u307F\u3001\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u306E\u3042\u308B\u30DC\u30C3\u30AF\u30B9\u306F `context` \u3067\u4E2D\u8EAB\u3092\u78BA\u304B\u3081\u3066\u304B\u3089\u7D9A\u3051\u308B\u3002\u5F15\u304D\u7D99\u304E\u30E1\u30E2\u306F\u4F1A\u8A71\u306E\u5C65\u6B74\u3084\u6D3B\u52D5\u30ED\u30B0\u306E\u4E0A\u9650\u3068\u306F\u5225\u306B\u3001\u8A08\u753B\u306E\u4E2D\u306B\u6B8B\u308B\n- MCP \u3067\u306F `boxglow_context` \u3068\u3001\u5404\u30C4\u30FC\u30EB\u306E `contextToken` \u5F15\u6570\u3092\u4F7F\u3046\n- \u300C\u4ED6\u306E Boxglow \u304C\u66F8\u304D\u8FBC\u307F\u4E2D (\u30ED\u30C3\u30AF\u4E2D)\u300D\u3068\u51FA\u3066\u3001\u5C11\u3057\u5F85\u3063\u3066\u3084\u308A\u76F4\u3057\u3066\u3082\u89E3\u3051\u306A\u3044\u3068\u304D\u306F\u3001`npx boxglow unlock` \u306E\u8868\u793A (\u6301\u3061\u4E3B\u3068\u5224\u5B9A) \u3092\u4EBA\u306B\u898B\u305B\u3066\u89E3\u9664\u3092\u983C\u3080\u3002\u81EA\u5206\u3067 `--actor human` \u3092\u4ED8\u3051\u305F\u308A\u3001\u30ED\u30C3\u30AF\u306E\u30D5\u30A9\u30EB\u30C0\u3092\u76F4\u63A5\u6D88\u3057\u305F\u308A\u3057\u306A\u3044\n\n\n### \u7740\u624B\u30FB\u5B8C\u4E86\u3001\u4ECA\u56DE\u306E\u7BC4\u56F2\u3001\u8A18\u9332\u306E\u9BAE\u5EA6\n\n- \u6700\u521D\u306B resume \u306E\u73FE\u6CC1 (\u6D3B\u52D5\u30FB\u5224\u65AD\u5F85\u3061\u30FB\u672A\u78BA\u8A8D\u56DE\u7B54) \u3092\u8AAD\u3080\u3002\u5019\u88DC\u306F\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2 / \u305D\u306E\u4ED6\u300D\u306E\u5404\u7D44\u3067\u300C\u7740\u624B\u3067\u304D\u308B / \u5165\u529B\u5F85\u3061\u300D\u306B\u5206\u304B\u308C\u3001\u4E0D\u8DB3\u3059\u308B\u5165\u529B\u540D\u304C\u51FA\u308B\u3002\u5165\u529B\u5F85\u3061\u3092\u3001\u6E96\u5099\u6E08\u307F\u3068\u53D6\u308A\u9055\u3048\u306A\u3044\u3002\n- \u30DC\u30C3\u30AF\u30B9\u306E\u4EFB\u610F4\u9805\u76EE\u3092 scope \u3067\u8A2D\u5B9A\u3059\u308B\u3002goal = \u4ECA\u56DE\u9054\u6210\u3059\u308B\u3053\u3068\u3001non-goals = \u4ECA\u56DE\u306F\u6271\u308F\u306A\u3044\u3053\u3068\u3001acceptance = \u5B8C\u4E86\u3068\u5224\u65AD\u3059\u308B\u6761\u4EF6\u3001consult = \u7BC4\u56F2\u3092\u5E83\u3052\u308B\u524D\u306B\u76F8\u8AC7\u3059\u308B\u6761\u4EF6\u3002context \u306E\u5148\u982D\u3067\u5BFE\u8C61\u3068\u89AA\u306E\u7BC4\u56F2\u3092\u8AAD\u307F\u3001\u7740\u624B\u524D\u3068\u5B8C\u4E86\u524D\u306B\u7167\u5408\u3059\u308B\u3002\u5B8C\u4E86\u6761\u4EF6\u306E\u672C\u6587\u3092AI\u304C\u6E80\u305F\u3057\u305F\u304B\u306F\u81EA\u52D5\u5224\u5B9A\u3057\u306A\u3044\u3002\n- scope B12 --goal "\u9054\u6210\u3059\u308B\u3053\u3068" --non-goals "\u4ECA\u56DE\u6271\u308F\u306A\u3044\u3053\u3068" --acceptance "\u78BA\u8A8D\u3059\u308B\u6761\u4EF6" --consult "\u76F8\u8AC7\u3059\u308B\u6761\u4EF6" \u3067\u8A2D\u5B9A\u3059\u308B\u3002scope B12 \u306F\u8868\u793A\u3060\u3051\u3002\u5404\u9805\u76EE\u306B none \u307E\u305F\u306F\u7A7A\u6587\u5B57\u3092\u6E21\u3059\u3068\u6D88\u3059\u3002\u753B\u9762\u3067\u306F\u8A73\u7D30\u30D1\u30CD\u30EB\u306E \u22EF \u2192\u300C\u4F5C\u696D\u7BC4\u56F2\u3092\u7DE8\u96C6\u300D\u3002\u8A18\u5165\u6E08\u307F\u306E\u9805\u76EE\u3060\u3051\u304C\u300C\u4ECA\u56DE\u306E\u7BC4\u56F2\u300D\u306B\u51FA\u308B\u3002\n- focus B12 \u3067\u3053\u306E\u30DC\u30C3\u30AF\u30B9\u3068\u914D\u4E0B\u3092\u6B21\u5019\u88DC\u306E\u5148\u982D\u306B\u3059\u308B\u3002focus \u306F\u73FE\u5728\u306E\u5BFE\u8C61\u306E\u8868\u793A\u3001focus none \u306F\u89E3\u9664\u3002\u5BFE\u8C61\u5916\u3082\u5225\u306E\u7D44\u306B\u6B8B\u308B\u3002\u5019\u88DC\u306B\u51FA\u308B\u3053\u3068\u81EA\u4F53\u306F\u4F5C\u696D\u7BC4\u56F2\u306E\u62E1\u5927\u3084\u516C\u958B\u3092\u8A31\u53EF\u3057\u306A\u3044\u3002\n- start \u306E\u5FC5\u9808\u5165\u529B\u4E0D\u8DB3\u306F\u65E2\u5B9A\u3067\u8B66\u544A\u3059\u308B\u304C\u3001\u5F93\u6765\u3069\u304A\u308A\u958B\u59CB\u3067\u304D\u308B\u3002\u5148\u884C\u4F5C\u696D\u304C\u5FC5\u8981\u306A\u3089 --reason "\u5148\u306B\u884C\u3046\u7406\u7531" \u3092\u4ED8\u3051\u308B\u3002\u8B66\u544A\u306F\u51FA\u305A\u3001\u7406\u7531\u306F\u6D3B\u52D5\u3068\u30ED\u30B0\u306B\u6B8B\u308B\u3002done \u306E\u51FA\u529B\u6210\u679C\u7269\u306A\u3057\u3082\u65E2\u5B9A\u3067\u8B66\u544A\u306E\u307F\u3002\u65E2\u5B58\u306E\u51FA\u529B\u6210\u679C\u7269\u307E\u305F\u306F --artifact \u3092\u6570\u3048\u3001\u30DC\u30C3\u30AF\u30B9\u306E\u53C2\u8003\u8CC7\u6599\u306F\u6570\u3048\u306A\u3044\u3002\n- \u8A08\u753B\u3067\u9078\u3076\u5834\u5408\u3060\u3051 policy --start reject --done reject \u306B\u3059\u308B\u3002\u305D\u308C\u305E\u308C warn \u306B\u623B\u305B\u308B\u3002\u5165\u529B\u5F85\u3061\u306E\u62D2\u5426\u3067\u3082\u7406\u7531\u4ED8\u304D\u306E\u958B\u59CB\u306F\u53EF\u80FD\u3002\u6210\u679C\u7269\u306A\u3057\u306E\u62D2\u5426\u306F set --status white \u306B\u3082\u52B9\u304F\u3002\u4EBA\u306E\u64CD\u4F5C (\u753B\u9762 / --actor human) \u306F\u62D2\u5426\u3057\u306A\u3044\u3002AI\u304C\u62D2\u5426\u3092\u907F\u3051\u308B\u305F\u3081\u306B\u4EBA\u3092\u540D\u4E57\u3063\u305F\u308A\u3001\u8A2D\u5B9A\u3092\u5F31\u3081\u305F\u308A\u3057\u306A\u3044\u3002\n- guard \u6709\u52B9\u6642\u3001scope \u306E\u5909\u66F4\u3001focus \u306E\u8A2D\u5B9A\u30FB\u89E3\u9664\u3001policy \u306E\u5909\u66F4\u306B\u3082 --context-token \u304C\u5FC5\u8981\u3002scope/focus/policy \u3092\u5909\u3048\u308B\u3068\u65B0\u3057\u3044\u30C8\u30FC\u30AF\u30F3\u304C\u51FA\u308B\u306E\u3067\u3001\u6B21\u306E\u64CD\u4F5C\u306F\u305D\u308C\u3092\u4F7F\u3046\u3002\u8868\u793A\u3060\u3051\u306B\u306F\u4E0D\u8981\u3002\n- resume \u306F\u5B8C\u4E86\u6E08\u307F\u306E\u5F15\u304D\u7D99\u304E\u672C\u6587\u3092\u96A0\u3057\u3001\u4EF6\u6570\u3060\u3051\u793A\u3059\u3002\u5C65\u6B74\u304C\u5FC5\u8981\u306A\u5834\u5408\u3060\u3051 resume --include-completed \u3067\u5C55\u958B\u3059\u308B (context / show \u3067\u306F\u500B\u5225\u306B\u8AAD\u3081\u308B)\u3002\n- \u8AAC\u660E\u306F descriptionUpdatedAt\u3001\u72B6\u614B\u306F statusChangedAt\u3001\u5F15\u304D\u7D99\u304E\u306F\u65E2\u5B58\u306E at \u3067\u65E5\u6642\u3092\u78BA\u8A8D\u3067\u304D\u308B\u3002\u72B6\u614B\u5909\u66F4\u3088\u308A\u53E4\u3044\u30E1\u30E2\u306F\u305D\u306E\u65E8\u3092\u793A\u3059\u3002\u65E7\u8A18\u9332\u306E\u4E0D\u660E\u306A\u65E5\u6642\u306F\u88DC\u308F\u306A\u3044\u3002\u300C\u65B0\u3057\u3044\u8A18\u9332\u300D\u306F\u771F\u507D\u306E\u4FDD\u8A3C\u3067\u306F\u306A\u3044\u3002\n- Done \u3067\u53E4\u3044\u72B6\u6CC1\u8AAC\u660E\u304C\u6B8B\u308B\u53EF\u80FD\u6027\u304C\u3042\u308B\u5834\u5408\u3001CLI\u30FB\u8A73\u7D30\u30D1\u30CD\u30EB\u30FBresume \u306B\u898B\u76F4\u3057\u6848\u5185\u304C\u51FA\u308B\u3002\u672C\u6587\u306F\u81EA\u52D5\u3067\u6D88\u3055\u306A\u3044\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089 set B12 --note "\u73FE\u5728\u306E\u8AAC\u660E" (none \u3067\u6D88\u53BB) \u307E\u305F\u306F\u753B\u9762\u306E Notes \u3067\u76F4\u3059\u3002\n- MCP \u306F boxglow_scope / boxglow_focus / boxglow_policy\u3001boxglow_start \u306E reason\u3001boxglow_resume \u306E includeCompleted \u3092\u4F7F\u3046\u3002\u5909\u66F4\u306B\u306F contextToken \u3092\u4ED8\u3051\u308B\u3002\n- \u30C7\u30FC\u30BF\u5F62\u5F0F\u306F v5 \u306E\u307E\u307E\u3067\u5168\u9805\u76EE\u306F\u4EFB\u610F\u3002\u65E7\u7248 (0.4.2\u4EE5\u524D) \u306F\u65B0\u3057\u3044\u898F\u5247\u3092\u5B9F\u884C\u305B\u305A\u3001\u4FDD\u5B58\u6642\u306B\u8A08\u753B\u306E workflowPolicy / focusBlockId \u3092\u843D\u3068\u3059\u3002\u3053\u308C\u3089\u3092\u4F7F\u3046\u8A08\u753B\u306E\u66F8\u304D\u624B (CLI\u30FBserve\u30FB\u753B\u9762\u30FB\u62E1\u5F35\u5185\u306E\u30A2\u30D7\u30EA) \u306F\u5BFE\u5FDC\u3057\u305F\u30D3\u30EB\u30C9\u306B\u63C3\u3048\u308B\u3002\u7248\u756A\u53F7\u3060\u3051\u3067\u306A\u304F\u30D3\u30EB\u30C9\u65E5\u6642\u3082\u78BA\u8A8D\u3059\u308B\u3002\n';
|
|
5697
|
+
|
|
5698
|
+
// .claude/skills/boxglow/SKILL.en.md
|
|
5699
|
+
var SKILL_en_default = '---\nname: boxglow\ndescription: Share the plan and progress with humans through Boxglow (boxglow.json). Record starting, splitting, finishing and pending decisions with npx boxglow. Use for "record progress in Boxglow", "check the status with boxglow", or "/boxglow".\n---\n\n# Share the plan and progress with Boxglow\n\n`boxglow.json` at the repository root is the source of truth for the plan. Humans watch it on the Boxglow screen (https://boxglow.pages.dev/, "Open boxglow.json").\nDo not edit it directly; update it with `npx boxglow`.\n\n## Making the first plan (for a new project)\n\n1. Read the direction (README, the request, requirement notes), run `npx boxglow init --name "<name>"`, then place the top-level items (3-7 of them) with `add`.\n For each item, always write its "inputs (what it uses)" and "output (a concrete deliverable: file, URL, PR)", and connect them all the way to the final deliverable (`connect`)\n2. Once created, get a human\'s confirmation with `npx boxglow ask <first top-level item> "Is this breakdown into top-level items OK?" --options "OK|Needs changes"`.\n The human can answer on the screen, with `npx boxglow answer <block> "<answer>"`, or in chat. If the answer comes back in chat, the AI records it with `answer --by human`.\n If the human does not use the screen (when you are told to proceed with the AI alone), the AI records its own decision with `answer --by claude-code "<decision and reason>"` and moves on. The point is to keep a record of decisions; the screen is not required\n3. `split` only the item you are about to start (leave later stages coarse, and refine them as you go)\n\n## Steps\n\n1. Read `npx boxglow resume` (or `npx boxglow status --brief`). Take in the handoff notes, the answers not yet read by the AI, the pending decisions, the boxes in progress and the next candidates. Use `npx boxglow status` when you need the whole tree\n2. Once you have chosen a box, check its inputs and outputs with `npx boxglow show <block>`, then run `npx boxglow start <block> --note "<what you will do>"`.\n On a plan with the context guard on, first read `npx boxglow context <block>` and pass the `contextToken` (context token) from its output as `--context-token <context token>`\n3. Split a large box with `npx boxglow split <block> --spec \'<JSON>\'` (see `npx boxglow help` for the format). Always decide the output of each box\n4. When the deliverable exists, run `npx boxglow done <block> --artifact "<name>=<URL or path>"` (with the guard on, add `--context-token <context token>`; if your previous command printed "New context token: <token>", use that one)\n5. When a human decision is needed, run `npx boxglow ask <block> "<question>" --options "A|B"` and move on to another box\n6. When you are stuck, run `npx boxglow blocked <block> --note "<what is in the way>"`\n7. Before an interruption, context compaction or a handoff, save a handoff note with `npx boxglow checkpoint <block> --note "Findings; next steps; unresolved questions"`\n8. At the end, summarize `npx boxglow status --brief` and report it\n\n## Rules\n\n- Write each `ask` question so that it can be decided on its own. Do not point outside the question, as in "Is this OK?" or "the plan above"; put the premises, comparison and impact in `--context` (the human reads only the question in a list and answers it)\n- When choosing one of several candidates (an approach, a library, a design choice), even if the AI decides by itself, record it as `ask <block> "<question>" --options "A|B|C"` \u2192 `answer --by claude-code "<the choice>. Reason: ..."` so that the candidates not chosen stay on the box (you can come back to them when the direction changes)\n- To change direction, use `reopen <block> --note "<reason>"`: the previous answer stays in the history and you choose again (candidates are not deleted)\n\n- Keep at most 1-2 boxes in progress at a time\n- Make deliverables (artifacts) something a human can open later (a commit, a PR, a file path)\n- Name outputs after concrete deliverables (file, PR, URL, test results). Avoid abstract names (feature set, findings)\n- The receiving side of a connection can be just a title (`connect "A.Design doc" "B"` creates an input "Design doc" on B). An input\'s name is determined by the source output\'s name and is not changed on the input side\n- Always connect boxes as "input \u2192 output". Add missing inputs / outputs with `port`, remove a wire with `disconnect`, and remove a box itself with `remove`. Export a document with `export --out docs/ROADMAP.md`\n- When you add a box, give it a kind of work with `--category` (design / build / verify / evaluate / study / research / ui / improve / fix / docs / ops / other). On the screen it becomes a colored band and a tag\n- If boxglow.json does not exist yet, create it with `npx boxglow init --name "<project name>"` and place the final deliverable and the top-level boxes with `add`\n\n## Context token and handoff\n\nThe context guard keeps an AI from continuing work on outdated instructions. `setup-agent` turns it on (on other plans, run `npx boxglow guard on`).\nWhile it is on, an AI\'s `start` / `done` / `set` / `split` / `artifact` / `ack` / `blocked` / `review` / `leave` / `checkpoint` need `--context-token <context token>`. Commands run by a person (`--actor human`) do not.\n\n```\nnpx boxglow context B12 # read the decisions (including acknowledged answers), input/output contracts and handoff notes. contextToken in the output is the context token\nnpx boxglow start B12 --note "<what you will do>" --context-token <context token>\nnpx boxglow ask B12 "<question>" --options "A|B" --context-token <context token>\n# -> last line of the output: New context token: <token>\nnpx boxglow done B12 --artifact "<name>=<path>" --context-token <new context token>\n```\n\n- **The context token changes (you must reread)** when a person answers a decision or revises an answer, a person changes the instructions (title, description, input/output contracts), or another AI or a person updates the handoff note. The same applies when this happens on a parent box or an input provider. The old context token is rejected: reread `context`, take the change into account in your work, then retry\n- **The context token does not change** on `start` / `blocked` / `review` / `leave` / `ack`, on changes to progress, status, dates or category, when `check` verifies deliverables, or when boxes are moved on the screen. Keep using the same context token\n- **You can update it yourself**: when your own `ask` / `artifact` / `done` / `set` / `split` / `checkpoint` etc. changes the context, the last line of its output is "New context token: <token>". Use it for the next command without rereading. `ask` / `decision` / `answer` / `reopen` run without a context token, but they print the new context token only when you pass the latest one\n- Do not turn the guard off to get past an old context token (an AI needs the latest context token to run `guard off`)\n- When resuming, read `npx boxglow resume`, and check a box that has a handoff note with `context` before continuing. Handoff notes stay in the plan, independent of the chat history and the activity-log limit\n- With MCP, use `boxglow_context` and the `contextToken` argument of each tool\n- If "Another Boxglow writer holds the file lock" persists after waiting and retrying, show the output of `npx boxglow unlock` (owner and verdict) to a person and ask them to remove it. Never add `--actor human` yourself or delete the lock folder directly\n\n\n### Readiness, current scope, and fresh resume information\n\n- Read current activity, pending decisions, and unread answers first. Candidates are grouped by current scope / other candidates, then ready / waiting for inputs, with missing input names. A waiting candidate is not ready.\n- Four optional scope fields are available: goal, non-goals, acceptance, and consult (when to consult before expanding scope). Read the target and parent scopes at the start of context before starting and finishing. Textual acceptance criteria are not automatically evaluated.\n- Use scope B12 --goal "Goal" --non-goals "Excluded" --acceptance "Checks" --consult "When to ask". scope B12 only reads; none or an empty string clears a field. In the inspector, use \u22EF \u2192 Edit work scope. Only populated fields appear in the collapsed scope section.\n- focus B12 prioritizes that box and its descendants; focus reads the selection, and focus none clears it. Other candidates remain in a separate group. Being listed does not authorize expanding scope or publishing.\n- Missing required inputs warn on start by default but still allow the start. Supply --reason "Why starting early is appropriate" to proceed without a warning and save the reason in both activity and the log. Artifact-free done also remains warning-only by default. Existing output artifacts or --artifact count; reference material on the box does not.\n- Only enable strict checks when selected for the plan: policy --start reject --done reject. Each can return to warn. A recorded start reason also overrides reject. The artifact requirement also applies to set --status white. Human operations (UI / --actor human) are never rejected. Do not impersonate a human or weaken policy to bypass rejection.\n- With guard enabled, scope writes, focus selection/clearing, and policy writes require --context-token. These changes print a new token: use it next. Read-only forms need no token.\n- resume hides completed handoff bodies and shows their count. Request resume --include-completed only when history is needed; context / show can still read individual notes.\n- descriptionUpdatedAt dates description changes; statusChangedAt dates status changes; handoffs use their existing at field. Notes predating a status change are marked. Unknown legacy dates stay unknown, and recency does not establish truth.\n- After Done, CLI, inspector, and resume flag descriptions that may still describe an earlier state. Text is never rewritten automatically. Review it, then update with set B12 --note "Current description" (none clears it) or Notes in the UI.\n- MCP: boxglow_scope / boxglow_focus / boxglow_policy, reason on boxglow_start, and includeCompleted on boxglow_resume. Pass contextToken for writes.\n- All data fields are optional; schema v5 is unchanged. Old versions (0.4.2 and earlier) do not enforce these checks and drop workflowPolicy / focusBlockId when saving. Align all writers (CLI, serve, web app, bundled extension app) to a supporting build before using these settings. Check build time as well as version.\n';
|
|
5700
|
+
|
|
5701
|
+
// src/model/export.ts
|
|
5702
|
+
var STATUS_LABEL = {
|
|
5703
|
+
black: "New (BlackBox: \u51FA\u529B\u3060\u3051\u6C7A\u3081\u3066\u3042\u308B)",
|
|
5704
|
+
gray: "In Progress (GrayBox: \u5206\u89E3\u4E2D\u30FB\u4F5C\u696D\u4E2D)",
|
|
5705
|
+
white: "Done (WhiteBox: \u51FA\u529B\u306B\u6210\u679C\u7269\u304C\u4ED8\u3044\u3066\u5B8C\u4E86)"
|
|
5706
|
+
};
|
|
5707
|
+
function portLine(p, port2) {
|
|
5708
|
+
const parts2 = [`- **${port2.name}**`];
|
|
5709
|
+
const desc = effectiveDescription(p, port2.id);
|
|
5710
|
+
if (desc) parts2.push(`: ${desc}`);
|
|
5711
|
+
if (port2.direction === "in" && !port2.required) parts2.push(" " + t("(\u4EFB\u610F)"));
|
|
5712
|
+
if (port2.artifacts.length > 0) {
|
|
5713
|
+
parts2.push(" / " + port2.artifacts.map((a) => a.url ? `[${a.title}](${a.url})` : a.title).join(", "));
|
|
5714
|
+
}
|
|
5715
|
+
const links = port2.direction === "in" ? incomingEdges(p, { portId: port2.id, side: "outer" }).map((e) => describeEndpointSource(p, e.from.portId)) : outgoingEdges(p, { portId: port2.id, side: "outer" }).map((e) => describeEndpointTarget(p, e.to.portId));
|
|
5716
|
+
if (links.length > 0) parts2.push(` (${port2.direction === "in" ? t("\u4F9B\u7D66\u5143: {list}", { list: links.join(", ") }) : t("\u884C\u304D\u5148: {list}", { list: links.join(", ") })})`);
|
|
5717
|
+
return parts2.join("");
|
|
5718
|
+
}
|
|
5719
|
+
function describeEndpointSource(p, portId) {
|
|
5720
|
+
const port2 = p.ports[portId];
|
|
5721
|
+
if (!port2) return "?";
|
|
5722
|
+
const block2 = p.blocks[port2.blockId];
|
|
5723
|
+
const name = block2?.id === ROOT_ID ? t("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u5165\u529B") : block2?.title ?? "?";
|
|
5724
|
+
return `${name}.${port2.name}`;
|
|
5725
|
+
}
|
|
5726
|
+
function describeEndpointTarget(p, portId) {
|
|
5727
|
+
const port2 = p.ports[portId];
|
|
5728
|
+
if (!port2) return "?";
|
|
5729
|
+
const block2 = p.blocks[port2.blockId];
|
|
5730
|
+
const name = block2?.id === ROOT_ID ? t("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u51FA\u529B") : block2?.title ?? "?";
|
|
5731
|
+
return `${name}.${port2.name}`;
|
|
5732
|
+
}
|
|
5733
|
+
function blockSection(p, blockId, level, withChildren) {
|
|
5734
|
+
const b = p.blocks[blockId];
|
|
5735
|
+
if (!b) return "";
|
|
5736
|
+
const h = "#".repeat(Math.min(level, 6));
|
|
5737
|
+
const lines = [];
|
|
5738
|
+
lines.push(`${h} ${b.id === ROOT_ID ? t("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8: {name}", { name: p.name }) : `${b.key ? b.key + " " : ""}${b.title}`}`);
|
|
5739
|
+
lines.push("");
|
|
5740
|
+
if (b.id !== ROOT_ID) {
|
|
5741
|
+
lines.push(`- ${t("\u72B6\u614B: {status}", { status: t(STATUS_LABEL[b.status]) })}`);
|
|
5742
|
+
const cat = categoryOf(b.category);
|
|
5743
|
+
if (cat) lines.push(`- ${t("\u30AB\u30C6\u30B4\u30EA: {label}", { label: t(cat.label) })}`);
|
|
5744
|
+
const names = b.assigneeIds.map((id) => p.members.find((m) => m.id === id)?.name).filter(Boolean);
|
|
5745
|
+
if (names.length > 0) lines.push(`- ${t("\u62C5\u5F53: {names}", { names: names.join(", ") })}`);
|
|
5746
|
+
const prog = computeProgress(p, b.id);
|
|
5747
|
+
if (childrenOf(p, b.id).length > 0) lines.push(`- ${t("\u9032\u6357: \u4E0B\u306E\u968E\u5C64 {white}/{total} \u304C\u5B8C\u4E86", { white: prog.white, total: prog.total })}`);
|
|
5748
|
+
}
|
|
5749
|
+
if (b.id !== ROOT_ID && b.decisions.length > 0) {
|
|
5750
|
+
lines.push(`- ${t("\u5224\u65AD:")}`);
|
|
5751
|
+
for (const d of b.decisions) {
|
|
5752
|
+
const c = candidatesOf(d);
|
|
5753
|
+
lines.push(` - ${d.question}${d.answer !== void 0 ? ` \u2192 ${d.answer}` : " " + t("(\u672A\u56DE\u7B54)")}${c.rejected.length ? t("\u3002\u6B8B\u3057\u305F\u5019\u88DC: {list}", { list: c.rejected.join(" / ") }) : ""}${(d.history ?? []).length ? t("\u3002\u4EE5\u524D\u306E\u7B54\u3048: {list}", { list: d.history.map((h2) => h2.answer).join(" \u2192 ") }) : ""}`);
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
for (const item of scopeEntries(b.scope)) lines.push("", "**" + item.label + "**", item.text);
|
|
5757
|
+
if (b.description) {
|
|
5758
|
+
lines.push("");
|
|
5759
|
+
const stale = freshnessText(b, b.descriptionUpdatedAt, "older");
|
|
5760
|
+
if (stale) lines.push(stale);
|
|
5761
|
+
lines.push(b.description);
|
|
5762
|
+
}
|
|
5763
|
+
const ins = portsOf(p, b.id, "in");
|
|
5764
|
+
const outs = portsOf(p, b.id, "out");
|
|
5765
|
+
lines.push("");
|
|
5766
|
+
lines.push(`**${t("\u5165\u529B")}** (${ins.length === 0 ? t("\u306A\u3057") : t("{n} \u4EF6", { n: ins.length })})`);
|
|
5767
|
+
for (const q of ins) lines.push(portLine(p, q));
|
|
5768
|
+
lines.push("");
|
|
5769
|
+
lines.push(`**${t("\u51FA\u529B")}** (${t("{n} \u4EF6", { n: outs.length })})`);
|
|
5770
|
+
for (const q of outs) lines.push(portLine(p, q));
|
|
5771
|
+
if (b.artifacts.length > 0) {
|
|
5772
|
+
lines.push("");
|
|
5773
|
+
lines.push(`**${t("\u95A2\u9023\u8CC7\u6599")}**`);
|
|
5774
|
+
for (const a of b.artifacts) lines.push(`- ${a.url ? `[${a.title}](${a.url})` : a.title}${a.note ? `: ${a.note}` : ""}`);
|
|
5775
|
+
}
|
|
5776
|
+
lines.push("");
|
|
5777
|
+
if (withChildren) {
|
|
5778
|
+
for (const c of childrenOf(p, b.id)) lines.push(blockSection(p, c.id, level + 1, true));
|
|
5779
|
+
}
|
|
5780
|
+
return lines.join("\n");
|
|
5781
|
+
}
|
|
5782
|
+
function projectToMarkdown(p) {
|
|
5783
|
+
const prog = computeProgress(p, ROOT_ID);
|
|
5784
|
+
const head = [
|
|
5785
|
+
`# ${p.name}`,
|
|
5786
|
+
"",
|
|
5787
|
+
p.description,
|
|
5788
|
+
"",
|
|
3530
5789
|
t("\u5168\u4F53\u306E\u9032\u6357: {white}/{total} \u30D6\u30ED\u30C3\u30AF\u304C\u5B8C\u4E86 (BlackBox {black} / GrayBox {gray} / WhiteBox {white})", { white: prog.white, total: prog.total, black: prog.black, gray: prog.gray }),
|
|
3531
5790
|
"",
|
|
3532
5791
|
t("\u30D6\u30ED\u30C3\u30AF\u306F\u300C\u5165\u529B\u304B\u3089\u51FA\u529B\u3092\u4F5C\u308B\u30BF\u30B9\u30AF\u300D\u3002BlackBox \u306F\u51FA\u529B\u3060\u3051\u6C7A\u307E\u3063\u3066\u3044\u3066\u4E2D\u8EAB\u304C\u672A\u5B9A\u3001GrayBox \u306F\u5206\u89E3\u4E2D\u30FB\u7740\u624B\u4E2D\u3001WhiteBox \u306F\u5B8C\u4E86\u3002"),
|
|
@@ -3670,167 +5929,9 @@ function terminalHeight(p, which) {
|
|
|
3670
5929
|
return HEADER_H + Math.max(n, 1) * ROW_H + PAD_BOTTOM;
|
|
3671
5930
|
}
|
|
3672
5931
|
|
|
3673
|
-
// src/model/merge.ts
|
|
3674
|
-
var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3675
|
-
function pick(path, base, ours, theirs, r) {
|
|
3676
|
-
if (same(ours, theirs)) return ours;
|
|
3677
|
-
if (same(ours, base)) {
|
|
3678
|
-
r.merged++;
|
|
3679
|
-
return theirs;
|
|
3680
|
-
}
|
|
3681
|
-
if (same(theirs, base)) return ours;
|
|
3682
|
-
const id = JSON.stringify(path);
|
|
3683
|
-
const adopted = r.choices[id] ?? (ours === void 0 && theirs !== void 0 ? "theirs" : "ours");
|
|
3684
|
-
r.conflicts.push({ id, path: path.join("."), segments: path, ours, theirs, adopted });
|
|
3685
|
-
return adopted === "theirs" ? theirs : ours;
|
|
3686
|
-
}
|
|
3687
|
-
function mergeObject(path, base, ours, theirs, r) {
|
|
3688
|
-
const out2 = {};
|
|
3689
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base ?? {})]);
|
|
3690
|
-
for (const k of keys) {
|
|
3691
|
-
const b = base?.[k];
|
|
3692
|
-
const o = ours[k];
|
|
3693
|
-
const t2 = theirs[k];
|
|
3694
|
-
const value = pick([...path, k], b, o, t2, r);
|
|
3695
|
-
if (value !== void 0) out2[k] = value;
|
|
3696
|
-
}
|
|
3697
|
-
return out2;
|
|
3698
|
-
}
|
|
3699
|
-
function mergeMap(path, base, ours, theirs, r) {
|
|
3700
|
-
const out2 = {};
|
|
3701
|
-
const ids = /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base ?? {})]);
|
|
3702
|
-
for (const id of ids) {
|
|
3703
|
-
const b = base?.[id];
|
|
3704
|
-
const o = ours[id];
|
|
3705
|
-
const t2 = theirs[id];
|
|
3706
|
-
if (o && t2 && path[0] !== "handoffs") {
|
|
3707
|
-
out2[id] = mergeObject([...path, id], b, o, t2, r);
|
|
3708
|
-
continue;
|
|
3709
|
-
}
|
|
3710
|
-
const value = pick([...path, id], b, o, t2, r);
|
|
3711
|
-
if (value !== void 0) out2[id] = value;
|
|
3712
|
-
}
|
|
3713
|
-
return out2;
|
|
3714
|
-
}
|
|
3715
|
-
function mergeArrayById(path, base, ours, theirs, r) {
|
|
3716
|
-
const toMap = (xs) => Object.fromEntries((xs ?? []).map((x) => [x.id, x]));
|
|
3717
|
-
const merged = mergeMap(path, base ? toMap(base) : void 0, toMap(ours), toMap(theirs), r);
|
|
3718
|
-
const order = [...ours.map((x) => x.id), ...theirs.map((x) => x.id).filter((id) => !ours.some((x) => x.id === id))];
|
|
3719
|
-
return order.filter((id) => merged[id]).map((id) => merged[id]);
|
|
3720
|
-
}
|
|
3721
|
-
function restoreDeleted(out2, deleter, keeper, explicitlyDeleted) {
|
|
3722
|
-
const blocks = out2.blocks;
|
|
3723
|
-
const ports = out2.ports;
|
|
3724
|
-
const edges = out2.edges;
|
|
3725
|
-
const onlyInKeeper = (id) => !deleter.blocks[id] && !!keeper.blocks[id] && !explicitlyDeleted("blocks", id);
|
|
3726
|
-
const seeds = /* @__PURE__ */ new Set();
|
|
3727
|
-
for (const id of Object.keys(blocks)) if (onlyInKeeper(id)) seeds.add(id);
|
|
3728
|
-
for (const port2 of Object.values(ports)) {
|
|
3729
|
-
const id = port2.blockId;
|
|
3730
|
-
if (!blocks[id] && onlyInKeeper(id) && !deleter.ports[port2.id]) seeds.add(id);
|
|
3731
|
-
}
|
|
3732
|
-
if (seeds.size === 0) return;
|
|
3733
|
-
const tops = /* @__PURE__ */ new Set();
|
|
3734
|
-
for (const seed of seeds) {
|
|
3735
|
-
let top = seed;
|
|
3736
|
-
for (let parent = keeper.blocks[top]?.parentId; parent && onlyInKeeper(parent); parent = keeper.blocks[top]?.parentId) top = parent;
|
|
3737
|
-
tops.add(top);
|
|
3738
|
-
}
|
|
3739
|
-
const restored = new Set(tops);
|
|
3740
|
-
for (let grew = true; grew; ) {
|
|
3741
|
-
grew = false;
|
|
3742
|
-
for (const b of Object.values(keeper.blocks)) {
|
|
3743
|
-
if (b.parentId && restored.has(b.parentId) && !restored.has(b.id) && !explicitlyDeleted("blocks", b.id)) {
|
|
3744
|
-
restored.add(b.id);
|
|
3745
|
-
grew = true;
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
}
|
|
3749
|
-
for (const id of restored) if (!blocks[id]) blocks[id] = keeper.blocks[id];
|
|
3750
|
-
for (const port2 of Object.values(keeper.ports)) if (restored.has(port2.blockId) && !ports[port2.id] && !explicitlyDeleted("ports", port2.id)) ports[port2.id] = port2;
|
|
3751
|
-
if (keeper.handoffs) {
|
|
3752
|
-
const handoffs = out2.handoffs ?? {};
|
|
3753
|
-
for (const id of restored) if (keeper.handoffs[id] && !handoffs[id] && !explicitlyDeleted("handoffs", id)) handoffs[id] = keeper.handoffs[id];
|
|
3754
|
-
out2.handoffs = handoffs;
|
|
3755
|
-
}
|
|
3756
|
-
for (const e of Object.values(keeper.edges)) {
|
|
3757
|
-
if (edges[e.id] || !ports[e.from.portId] || !ports[e.to.portId] || explicitlyDeleted("edges", e.id)) continue;
|
|
3758
|
-
const from = keeper.ports[e.from.portId]?.blockId;
|
|
3759
|
-
const to = keeper.ports[e.to.portId]?.blockId;
|
|
3760
|
-
if (from && restored.has(from) || to && restored.has(to)) edges[e.id] = e;
|
|
3761
|
-
}
|
|
3762
|
-
}
|
|
3763
|
-
function mergeProjects(base, ours, theirs, choices = {}) {
|
|
3764
|
-
const r = { project: ours, merged: 0, conflicts: [], choices };
|
|
3765
|
-
const b = base ?? void 0;
|
|
3766
|
-
const o = ours;
|
|
3767
|
-
const th = theirs;
|
|
3768
|
-
const out2 = { ...o };
|
|
3769
|
-
for (const k of ["name", "description", "visibility", "terminals", "contextGuard", "workflowPolicy", "focusBlockId"]) out2[k] = pick([k], b?.[k], o[k], th[k], r);
|
|
3770
|
-
for (const k of ["blocks", "ports", "edges", "agents", "handoffs"]) {
|
|
3771
|
-
out2[k] = mergeMap([k], b?.[k], o[k] ?? {}, th[k] ?? {}, r);
|
|
3772
|
-
}
|
|
3773
|
-
const explicitlyDeleted = (kind, id) => {
|
|
3774
|
-
const choice = choices[JSON.stringify([kind, id])];
|
|
3775
|
-
if (!choice) return false;
|
|
3776
|
-
return (choice === "ours" ? o : th)[kind]?.[id] === void 0;
|
|
3777
|
-
};
|
|
3778
|
-
restoreDeleted(out2, ours, theirs, explicitlyDeleted);
|
|
3779
|
-
restoreDeleted(out2, theirs, ours, explicitlyDeleted);
|
|
3780
|
-
const mergedBlocks = out2.blocks;
|
|
3781
|
-
for (const [id, box] of Object.entries(mergedBlocks)) {
|
|
3782
|
-
for (const [field, date] of [["description", "descriptionUpdatedAt"], ["status", "statusChangedAt"]]) {
|
|
3783
|
-
const left = ours.blocks[id], right = theirs.blocks[id];
|
|
3784
|
-
const matching = [left, right].filter((b2) => b2 && b2[field] === box[field]);
|
|
3785
|
-
if (!matching.length) continue;
|
|
3786
|
-
const times = matching.map((b2) => b2[date]).filter((x) => typeof x === "string").sort((a, b2) => Date.parse(a) - Date.parse(b2));
|
|
3787
|
-
if (times.length) box[date] = times[times.length - 1];
|
|
3788
|
-
else delete box[date];
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
|
-
if (out2.focusBlockId && !mergedBlocks[out2.focusBlockId]) delete out2.focusBlockId;
|
|
3792
|
-
const mergedPorts = out2.ports;
|
|
3793
|
-
const mergedEdges = out2.edges;
|
|
3794
|
-
for (const [id, port2] of Object.entries(mergedPorts)) if (!mergedBlocks[port2.blockId]) delete mergedPorts[id];
|
|
3795
|
-
for (const [id, e] of Object.entries(mergedEdges)) {
|
|
3796
|
-
const edge2 = e;
|
|
3797
|
-
if (!mergedPorts[edge2.from?.portId] || !mergedPorts[edge2.to?.portId]) delete mergedEdges[id];
|
|
3798
|
-
}
|
|
3799
|
-
const mergedHandoffs = out2.handoffs;
|
|
3800
|
-
for (const id of Object.keys(mergedHandoffs)) {
|
|
3801
|
-
if (!mergedBlocks[id] && (ours.blocks[id] || theirs.blocks[id] || base?.blocks[id])) delete mergedHandoffs[id];
|
|
3802
|
-
}
|
|
3803
|
-
if (Object.keys(mergedHandoffs).length === 0 && !ours.handoffs && !theirs.handoffs) delete out2.handoffs;
|
|
3804
|
-
out2.members = mergeArrayById(["members"], base?.members, ours.members ?? [], theirs.members ?? [], r);
|
|
3805
|
-
out2.inputGroups = mergeArrayById(["inputGroups"], base?.inputGroups, ours.inputGroups ?? [], theirs.inputGroups ?? [], r);
|
|
3806
|
-
const log = /* @__PURE__ */ new Map();
|
|
3807
|
-
for (const e of [...ours.log ?? [], ...theirs.log ?? []]) log.set(e.id, e);
|
|
3808
|
-
out2.log = [...log.values()].sort((p, q) => p.at < q.at ? -1 : p.at > q.at ? 1 : 0);
|
|
3809
|
-
const blocks = out2.blocks;
|
|
3810
|
-
const used = /* @__PURE__ */ new Map();
|
|
3811
|
-
let nextKey2 = Math.max(ours.nextKey ?? 1, theirs.nextKey ?? 1);
|
|
3812
|
-
for (const id of Object.keys(ours.blocks ?? {})) {
|
|
3813
|
-
const key = blocks[id]?.key;
|
|
3814
|
-
if (key) used.set(key, id);
|
|
3815
|
-
}
|
|
3816
|
-
for (const id of Object.keys(blocks)) {
|
|
3817
|
-
const key = blocks[id].key;
|
|
3818
|
-
if (!key) continue;
|
|
3819
|
-
const owner = used.get(key);
|
|
3820
|
-
if (owner && owner !== id) {
|
|
3821
|
-
blocks[id] = { ...blocks[id], key: `B${nextKey2}` };
|
|
3822
|
-
r.conflicts.push({ id: JSON.stringify(["blocks", id, "key"]), segments: ["blocks", id, "key"], automatic: true, path: `blocks.${id}.key`, ours: key, theirs: t("{key} \u306B\u632F\u308A\u76F4\u3057", { key: `B${nextKey2}` }) });
|
|
3823
|
-
nextKey2++;
|
|
3824
|
-
} else used.set(key, id);
|
|
3825
|
-
}
|
|
3826
|
-
out2.nextKey = nextKey2;
|
|
3827
|
-
r.project = out2;
|
|
3828
|
-
return { project: r.project, merged: r.merged, conflicts: r.conflicts };
|
|
3829
|
-
}
|
|
3830
|
-
|
|
3831
5932
|
// cli/main.ts
|
|
3832
5933
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3833
|
-
import { dirname as
|
|
5934
|
+
import { dirname as dirname6, join as join7, resolve as resolve5 } from "node:path";
|
|
3834
5935
|
|
|
3835
5936
|
// src/model/report.ts
|
|
3836
5937
|
var quoted = (title) => t("\u300C{title}\u300D", { title });
|
|
@@ -3993,8 +6094,8 @@ function logReport(p, n = 20) {
|
|
|
3993
6094
|
|
|
3994
6095
|
// cli/git.ts
|
|
3995
6096
|
import { execFileSync } from "node:child_process";
|
|
3996
|
-
import { existsSync as
|
|
3997
|
-
import { dirname as
|
|
6097
|
+
import { existsSync as existsSync8, statSync as statSync5 } from "node:fs";
|
|
6098
|
+
import { dirname as dirname5, relative as relative2, resolve as resolve4 } from "node:path";
|
|
3998
6099
|
function git(args, cwd) {
|
|
3999
6100
|
try {
|
|
4000
6101
|
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
@@ -4012,9 +6113,9 @@ function remoteToWeb(remote) {
|
|
|
4012
6113
|
return null;
|
|
4013
6114
|
}
|
|
4014
6115
|
function gitRefFor(filePath) {
|
|
4015
|
-
const abs =
|
|
4016
|
-
if (!
|
|
4017
|
-
const root = git(["rev-parse", "--show-toplevel"],
|
|
6116
|
+
const abs = resolve4(filePath);
|
|
6117
|
+
if (!existsSync8(abs) || !statSync5(abs).isFile()) return null;
|
|
6118
|
+
const root = git(["rev-parse", "--show-toplevel"], dirname5(abs));
|
|
4018
6119
|
if (!root) return null;
|
|
4019
6120
|
const rel = relative2(root, abs).replace(/\\/g, "/");
|
|
4020
6121
|
const commit = git(["rev-parse", "HEAD"], root) ?? "";
|
|
@@ -4028,14 +6129,14 @@ function gitRefFor(filePath) {
|
|
|
4028
6129
|
}
|
|
4029
6130
|
function findGitRef(recorded, bases) {
|
|
4030
6131
|
for (const base of bases) {
|
|
4031
|
-
const ref = gitRefFor(
|
|
6132
|
+
const ref = gitRefFor(resolve4(base, recorded));
|
|
4032
6133
|
if (ref) return ref;
|
|
4033
6134
|
}
|
|
4034
6135
|
return null;
|
|
4035
6136
|
}
|
|
4036
6137
|
function currentRef(root, rel) {
|
|
4037
6138
|
const commit = git(["rev-parse", "HEAD"], root) ?? "";
|
|
4038
|
-
const blob = git(["hash-object", "--path", rel, "--",
|
|
6139
|
+
const blob = git(["hash-object", "--path", rel, "--", resolve4(root, rel)], root) ?? "";
|
|
4039
6140
|
const repo = git(["config", "--get", "remote.origin.url"], root) ?? "";
|
|
4040
6141
|
const web = repo ? remoteToWeb(repo) : null;
|
|
4041
6142
|
return { path: rel, commit, blob, url: web && commit ? `${web}/blob/${commit}/${rel}` : "" };
|
|
@@ -4043,7 +6144,7 @@ function currentRef(root, rel) {
|
|
|
4043
6144
|
function checkGitRef(ref, cwd) {
|
|
4044
6145
|
const root = git(["rev-parse", "--show-toplevel"], cwd);
|
|
4045
6146
|
if (!root || !ref.path) return { state: "missing" };
|
|
4046
|
-
if (
|
|
6147
|
+
if (existsSync8(resolve4(root, ref.path))) return { state: "ok", path: ref.path };
|
|
4047
6148
|
if (ref.blob) {
|
|
4048
6149
|
const tree = git(["ls-tree", "-r", "HEAD"], root) ?? "";
|
|
4049
6150
|
for (const line of tree.split("\n")) {
|
|
@@ -4061,7 +6162,7 @@ function checkGitRef(ref, cwd) {
|
|
|
4061
6162
|
if (m && m[1] === cur) cur = m[2];
|
|
4062
6163
|
}
|
|
4063
6164
|
}
|
|
4064
|
-
if (cur !== ref.path &&
|
|
6165
|
+
if (cur !== ref.path && existsSync8(resolve4(root, cur))) return { state: "moved", ...currentRef(root, cur) };
|
|
4065
6166
|
}
|
|
4066
6167
|
return { state: "missing" };
|
|
4067
6168
|
}
|
|
@@ -4224,17 +6325,17 @@ function parseArgs(argv2) {
|
|
|
4224
6325
|
var str = (v) => typeof v === "string" ? v : Array.isArray(v) ? v[v.length - 1] : void 0;
|
|
4225
6326
|
var list = (v) => typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
|
|
4226
6327
|
function locateFile(opt2, forInit = false) {
|
|
4227
|
-
if (opt2) return
|
|
4228
|
-
if (process.env.BOXGLOW_FILE) return
|
|
4229
|
-
let
|
|
6328
|
+
if (opt2) return resolve5(opt2);
|
|
6329
|
+
if (process.env.BOXGLOW_FILE) return resolve5(process.env.BOXGLOW_FILE);
|
|
6330
|
+
let dir2 = process.cwd();
|
|
4230
6331
|
for (let i = 0; i < 12; i++) {
|
|
4231
|
-
const candidate =
|
|
4232
|
-
if (
|
|
4233
|
-
const parent =
|
|
4234
|
-
if (parent ===
|
|
4235
|
-
|
|
6332
|
+
const candidate = join7(dir2, "boxglow.json");
|
|
6333
|
+
if (existsSync9(candidate)) return candidate;
|
|
6334
|
+
const parent = dirname6(dir2);
|
|
6335
|
+
if (parent === dir2) break;
|
|
6336
|
+
dir2 = parent;
|
|
4236
6337
|
}
|
|
4237
|
-
if (forInit) return
|
|
6338
|
+
if (forInit) return join7(process.cwd(), "boxglow.json");
|
|
4238
6339
|
throw new Error(t("boxglow.json \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (--file \u3067\u6307\u5B9A\u3059\u308B\u304B\u3001`boxglow init` \u3067\u4F5C\u3063\u3066\u304F\u3060\u3055\u3044)"));
|
|
4239
6340
|
}
|
|
4240
6341
|
function actorOf(opt2) {
|
|
@@ -4269,15 +6370,15 @@ var readRevisions = /* @__PURE__ */ new Map();
|
|
|
4269
6370
|
var lastSavedText = null;
|
|
4270
6371
|
var CONFLICT_RETRIES = 8;
|
|
4271
6372
|
function load(path) {
|
|
4272
|
-
const text2 =
|
|
4273
|
-
readRevisions.set(
|
|
6373
|
+
const text2 = readFileSync9(path, "utf8");
|
|
6374
|
+
readRevisions.set(resolve5(path), revisionOf(text2));
|
|
4274
6375
|
return fromJSON(text2);
|
|
4275
6376
|
}
|
|
4276
6377
|
function save(path, p) {
|
|
4277
|
-
const expected = readRevisions.has(
|
|
6378
|
+
const expected = readRevisions.has(resolve5(path)) ? readRevisions.get(resolve5(path)) : null;
|
|
4278
6379
|
const text2 = toJSON(resolveAllOverlaps(normalizeCollapsed(normalizeInputNames(p).project), (q, id) => blockSize(q, id))) + "\n";
|
|
4279
6380
|
const revision = commitFile(path, text2, expected);
|
|
4280
|
-
readRevisions.set(
|
|
6381
|
+
readRevisions.set(resolve5(path), revision);
|
|
4281
6382
|
lastSavedText = text2;
|
|
4282
6383
|
}
|
|
4283
6384
|
function mustFind(p, ref, what = "block") {
|
|
@@ -4365,12 +6466,21 @@ boxglow CLI: AI \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8 (Claude Code / Codex \u306A
|
|
|
4365
6466
|
policy [--start warn|reject] [--done warn|reject] \u5165\u529B\u5F85\u3061\u306E\u958B\u59CB / \u6210\u679C\u7269\u306A\u3057\u306E\u5B8C\u4E86\u3002\u65E2\u5B9A warn\u3002\u4EBA\u306E\u64CD\u4F5C\u306F\u62D2\u5426\u3057\u306A\u3044
|
|
4366
6467
|
reject \u306E start \u3082 --reason \u304C\u3042\u308C\u3070\u901A\u3059\u3002done \u306F\u65E2\u5B58\u306E\u51FA\u529B\u6210\u679C\u7269\u3082\u6570\u3048\u308B\u3002set --status white \u306B\u3082\u9069\u7528
|
|
4367
6468
|
guard \u6709\u52B9\u6642\u306E scope \u8A2D\u5B9A\u30FBfocus \u8A2D\u5B9A\u30FBpolicy \u8A2D\u5B9A\u306F --context-token \u304C\u5FC5\u8981
|
|
4368
|
-
context <block>
|
|
6469
|
+
context <block> [--brief] \u30DC\u30C3\u30AF\u30B9\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 (\u89AA\u3068\u5165\u529B\u5143\u306E\u8AAC\u660E\u30FB\u5224\u65AD\u30FB\u5165\u51FA\u529B\u306E\u6761\u4EF6\u30FB\u5F15\u304D\u7D99\u304E) \u3068\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3 contextToken \u3092 JSON \u3067\u51FA\u3059
|
|
6470
|
+
--brief = \u77ED\u3044\u5F62: \u5BFE\u8C61\u306E\u60C5\u5831\u306F\u5168\u90E8\u3001\u89AA\u30FB\u4E0A\u6D41\u306F\u984C\u540D\u30FB\u72B6\u614B\u30FB\u6709\u52B9\u306A\u5224\u65AD\u30FB\u5BFE\u8C61\u306B\u3064\u306A\u304C\u308B\u51FA\u529B\u3060\u3051\u3002\u7701\u3044\u305F\u3082\u306E\u306E\u4EF6\u6570\u3068\u53D6\u308A\u65B9\u3092\u51FA\u3059 (\u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306F\u540C\u3058)
|
|
4369
6471
|
checkpoint <block> --note <\u30E1\u30E2> \u5F15\u304D\u7D99\u304E\u30E1\u30E2 (\u5206\u304B\u3063\u305F\u3053\u3068\u30FB\u6B21\u306E\u624B\u9806\u30FB\u672A\u89E3\u6C7A\u306E\u70B9) \u3092\u8A08\u753B\u306B\u6B8B\u3059\u3002\u4E2D\u65AD\u3084\u5F15\u304D\u7D99\u304E\u306E\u524D\u306B\u4F7F\u3046
|
|
4370
6472
|
guard on|off \u78BA\u8A8D\u30C8\u30FC\u30AF\u30F3\u306E\u8981\u6C42\u3092\u6709\u52B9 / \u7121\u52B9\u306B\u3059\u308B (setup-agent \u306F\u6709\u52B9\u306B\u3059\u308B)\u3002\u6709\u52B9\u306A\u9593\u3001AI \u306E start / done / set / split / artifact / ack /
|
|
4371
6473
|
blocked / review / leave / checkpoint \u306F --context-token <context \u3067\u5F97\u305F contextToken> \u304C\u8981\u308B (\u8AAD\u3093\u3060\u5F8C\u306B\u6307\u793A\u30FB\u56DE\u7B54\u30FB\u5F15\u304D\u7D99\u304E\u304C\u5909\u308F\u3063\u3066\u3044\u305F\u3089\u62D2\u5426)\u3002
|
|
4372
6474
|
\u4EBA (--actor human) \u306B\u306F\u8981\u6C42\u3057\u306A\u3044\u3002AI \u304C off \u306B\u3059\u308B\u3068\u304D\u306F --context-token \u304C\u8981\u308B
|
|
4373
6475
|
version [--json] boxglow \u306E\u7248\u3068\u3001\u4FDD\u5B58\u306E\u53D6\u308A\u6C7A\u3081\u306E\u7248\u3092\u51FA\u3059 (--version \u3067\u3082\u53EF)
|
|
6476
|
+
sync [--server <URL>] [--project <ID>] [--watch] (\u8A66\u9A13\u4E2D) \u8A08\u753B\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u540C\u671F\u30B5\u30FC\u30D0\u30FC\u3068\u305D\u308D\u3048\u308B\u3002\u521D\u56DE\u306F --server \u3067\u7D50\u3073\u4ED8\u3051\u308B\u3002\u6B62\u307E\u3063\u305F\u3089\u7406\u7531\u3068\u6B21\u306E\u64CD\u4F5C\u3092\u8868\u793A
|
|
6477
|
+
--watch = \u5E38\u6642\u306E\u540C\u671F (Ctrl+C \u3067\u7D42\u4E86\u3002\u3053\u306E\u7AEF\u672B\u306E\u3001\u540C\u3058\u30B5\u30FC\u30D0\u30FC\u306B\u7D50\u3073\u4ED8\u3044\u305F\u8A08\u753B\u3059\u3079\u3066\u3092\u53D7\u3051\u6301\u3064)
|
|
6478
|
+
--adopt <\u5370> = \u6D88\u3048\u305F\u8A2D\u5B9A\u306E\u524A\u9664\u3092\u63A1\u3063\u3066\u9001\u308B / --restore <\u5370> = \u6D88\u3048\u305F\u8A2D\u5B9A\u3092\u624B\u5143\u306B\u623B\u3059
|
|
6479
|
+
--resolve <\u5370> --prefer local|remote = \u7AF6\u5408\u3092\u624B\u5143 / \u30B5\u30FC\u30D0\u30FC\u306E\u5024\u306B\u6C7A\u3081\u308B / --link <\u5370> --prefer local|remote = \u521D\u56DE\u306B\u4E2D\u8EAB\u304C\u9055\u3046\u3068\u304D\u306E\u9078\u629E
|
|
6480
|
+
--recover <\u5370> --applied|--not-applied = \u9014\u4E2D\u3067\u7D42\u308F\u3063\u305F\u53D7\u3051\u53D6\u308A\u3092\u7D9A\u3051\u308B
|
|
6481
|
+
--account <\u5229\u7528\u8005\u306E ID> = \u5229\u7528\u8005\u306E\u8A18\u9332\u304C\u7121\u3044\u7D50\u3073\u4ED8\u3051\u3092\u3001\u8868\u793A\u3055\u308C\u305F\u5229\u7528\u8005\u306E\u3082\u306E\u3068\u3057\u3066\u7D9A\u3051\u308B
|
|
6482
|
+
login [--server <URL>] [--name <\u7AEF\u672B\u306E\u540D\u524D>] (\u8A66\u9A13\u4E2D) \u540C\u671F\u30B5\u30FC\u30D0\u30FC\u306B\u30B5\u30A4\u30F3\u30A4\u30F3\u3059\u308B (GitHub \u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u3002\u8868\u793A\u3055\u308C\u305F\u30B3\u30FC\u30C9\u3092\u3001\u30D6\u30E9\u30A6\u30B6\u3067\u5165\u529B\u3059\u308B)
|
|
6483
|
+
logout [--server <URL>] / whoami [--server <URL>] \u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u3059\u308B (\u30B5\u30FC\u30D0\u30FC\u5074\u306E\u30C8\u30FC\u30AF\u30F3\u3082\u53D6\u308A\u6D88\u3059) / \u4ECA\u306E\u5229\u7528\u8005\u30FB\u7AEF\u672B\u30FB\u4FDD\u5B58\u91CF\u3092\u51FA\u3059
|
|
4374
6484
|
unlock [--remove --lock-token <\u5370> --actor human] \u6B8B\u3063\u305F\u4FDD\u5B58\u30ED\u30C3\u30AF\u306E\u72B6\u614B (\u6301\u3061\u4E3B\u30FB\u5224\u5B9A) \u3092\u51FA\u3059 (\u8AAD\u3080\u3060\u3051\u3002AI \u3082\u4F7F\u3048\u308B)\u3002--remove \u306F\u4EBA\u304C\u89E3\u9664\u3059\u308B:
|
|
4375
6485
|
\u3053\u306E\u8A08\u753B\u3092\u958B\u3044\u3066\u3044\u308B Boxglow \u3092\u6B62\u3081\u3066\u304B\u3089\u3001\u8868\u793A\u3055\u308C\u305F\u5370\u3092\u4ED8\u3051\u3066\u5B9F\u884C\u3059\u308B\u3002\u6301\u3061\u4E3B\u304C\u52D5\u3044\u3066\u3044\u308B\u30ED\u30C3\u30AF\u306F\u89E3\u9664\u3067\u304D\u306A\u3044
|
|
4376
6486
|
status [--brief] [--json] \u5168\u4F53\u306E\u72B6\u6CC1 (Markdown)\u3002--brief \u306F\u5168\u968E\u5C64\u3092\u7701\u7565\u3057\u3001\u5224\u65AD\u30FB\u56DE\u7B54\u30FB\u6D3B\u52D5\u30FB\u6B21\u306E\u5019\u88DC\u3092\u8868\u793A
|
|
@@ -4440,12 +6550,21 @@ Usage (npx boxglow <command> ...):
|
|
|
4440
6550
|
policy [--start warn|reject] [--done warn|reject] Missing-input starts / artifact-free completion; default warn. Human actions are never rejected
|
|
4441
6551
|
A start reason overrides reject. Existing output artifacts count for done; set --status white also checks policy
|
|
4442
6552
|
With guard on, scope/focus/policy writes require --context-token
|
|
4443
|
-
context <block>
|
|
6553
|
+
context <block> [--brief] Print a box's context (descriptions, decisions, input/output contracts and handoff notes of its parents and input providers) and its contextToken as JSON
|
|
6554
|
+
--brief = short form: everything about the box itself; parents and input providers reduced to title, status, answered decisions and the outputs feeding it; says what was left out (same contextToken)
|
|
4444
6555
|
checkpoint <block> --note <note> Save a handoff note (findings, next steps, unresolved questions) in the plan. Use it before an interruption or a handoff
|
|
4445
6556
|
guard on|off Turn the context-token requirement on / off (setup-agent turns it on). While on, an AI's start / done / set / split / artifact / ack /
|
|
4446
6557
|
blocked / review / leave / checkpoint need --context-token <contextToken from context> (rejected if instructions, answers or handoff notes changed after reading).
|
|
4447
6558
|
People (--actor human) are not asked for it. An AI needs --context-token to turn it off
|
|
4448
6559
|
version [--json] Print the boxglow version and the save-protocol version (--version also works)
|
|
6560
|
+
sync [--server <URL>] [--project <ID>] [--watch] (experimental) Bring the plan file in line with a sync server. Bind with --server the first time. When it stops, it prints why and what to do
|
|
6561
|
+
--watch = keep syncing (Ctrl+C to stop; covers every plan on this machine bound to the same server)
|
|
6562
|
+
--adopt <token> = send the deletion of settings / --restore <token> = put the deleted settings back locally
|
|
6563
|
+
--resolve <token> --prefer local|remote = settle conflicts / --link <token> --prefer local|remote = choose a side on first link
|
|
6564
|
+
--recover <token> --applied|--not-applied = continue an interrupted pull
|
|
6565
|
+
--account <account id> = continue a binding that has no account recorded, as the account shown
|
|
6566
|
+
login [--server <URL>] [--name <device name>] (experimental) Sign in to a sync server (GitHub account; enter the code shown in your browser)
|
|
6567
|
+
logout [--server <URL>] / whoami [--server <URL>] Sign out (also revokes the token on the server) / show the current account, devices and storage
|
|
4449
6568
|
unlock [--remove --lock-token <token> --actor human] Show a leftover save lock (owner and verdict; read-only, agents may use it). --remove is for a person:
|
|
4450
6569
|
stop every Boxglow that has this plan open, then run it with the token shown. A lock whose owner is running cannot be removed
|
|
4451
6570
|
status [--brief] [--json] Overall status (Markdown). --brief omits the tree; keeps decisions, answers, activity and next actions
|
|
@@ -4556,8 +6675,8 @@ function main(argv2) {
|
|
|
4556
6675
|
}
|
|
4557
6676
|
if (cmd === "init") {
|
|
4558
6677
|
const path2 = locateFile(str(options.file), true);
|
|
4559
|
-
if (
|
|
4560
|
-
readRevisions.set(
|
|
6678
|
+
if (existsSync9(path2) && !options.force) throw new Error(t("{path} \u306F\u65E2\u306B\u3042\u308A\u307E\u3059 (--force \u3067\u4E0A\u66F8\u304D)", { path: path2 }));
|
|
6679
|
+
readRevisions.set(resolve5(path2), existsSync9(path2) ? revisionOf(readFileSync9(path2, "utf8")) : null);
|
|
4561
6680
|
const lang = explicitLang(options) ?? envLang();
|
|
4562
6681
|
setLang(lang);
|
|
4563
6682
|
const p2 = createProject(str(options.name) ?? t("\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8"));
|
|
@@ -4644,7 +6763,7 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4644
6763
|
return;
|
|
4645
6764
|
}
|
|
4646
6765
|
case "context": {
|
|
4647
|
-
out(JSON.stringify(contextReceipt(p, mustFind(p, rest[0]).id), null, 2));
|
|
6766
|
+
out(JSON.stringify((options.brief ? briefReceipt : contextReceipt)(p, mustFind(p, rest[0]).id), null, 2));
|
|
4648
6767
|
return;
|
|
4649
6768
|
}
|
|
4650
6769
|
case "guard": {
|
|
@@ -4677,7 +6796,7 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4677
6796
|
const text2 = fmt === "json" ? toJSON(p) + "\n" : projectToMarkdown(p);
|
|
4678
6797
|
const target = str(options.out);
|
|
4679
6798
|
if (target) {
|
|
4680
|
-
|
|
6799
|
+
writeFileSync4(target, text2, "utf8");
|
|
4681
6800
|
out(t("\u66F8\u304D\u51FA\u3057: {target}", { target }));
|
|
4682
6801
|
} else out(text2);
|
|
4683
6802
|
return;
|
|
@@ -4687,7 +6806,7 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4687
6806
|
return;
|
|
4688
6807
|
}
|
|
4689
6808
|
case "setup-agent": {
|
|
4690
|
-
const root = str(options.dir) ?
|
|
6809
|
+
const root = str(options.dir) ? resolve5(str(options.dir)) : dirname6(path);
|
|
4691
6810
|
const done = setupAgent({ root, agent: str(options.agent) ?? "all", snippet: AGENTS_SNIPPET(), skill: SKILL_MD() });
|
|
4692
6811
|
const guardWasOn = !!p.contextGuard;
|
|
4693
6812
|
p.contextGuard = true;
|
|
@@ -4710,13 +6829,13 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4710
6829
|
const tags = str(options.tags) ? str(options.tags).split(/[,、]/).map((s) => s.trim()).filter(Boolean) : [];
|
|
4711
6830
|
const tpl = extractTemplate(p, b.id, { tags });
|
|
4712
6831
|
const target = str(options.out) ?? `${b.title.replace(/[\\/:*?"<>|\s]/g, "_")}.boxglow-block.json`;
|
|
4713
|
-
|
|
6832
|
+
writeFileSync4(target, JSON.stringify(tpl, null, 2) + "\n", "utf8");
|
|
4714
6833
|
out(t("\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u66F8\u304D\u51FA\u3057: {target} ({count} \u500B\u306E\u5B50)", { target, count: tpl.root.children.length }));
|
|
4715
6834
|
return;
|
|
4716
6835
|
}
|
|
4717
6836
|
case "import-block": {
|
|
4718
6837
|
if (!rest[0]) throw new Error(t("<path> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4719
|
-
const tpl = parseTemplate(
|
|
6838
|
+
const tpl = parseTemplate(readFileSync9(rest[0], "utf8"));
|
|
4720
6839
|
const parentId = str(options.parent) ? mustFind(p, str(options.parent)).id : defaultTaskParent(p);
|
|
4721
6840
|
const r = instantiateTemplate(p, parentId, tpl, actor, nextFreePosition(p, parentId));
|
|
4722
6841
|
save(path, r.project);
|
|
@@ -4745,13 +6864,13 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4745
6864
|
const gp = inputGroupsOf(p).find((g) => g.name === rest.join(" "));
|
|
4746
6865
|
if (!gp) throw new Error(t("\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093"));
|
|
4747
6866
|
const target = str(options.out) ?? `${gp.name.replace(/[\\/:*?"<>|\s]/g, "_")}.boxglow-inputs.json`;
|
|
4748
|
-
|
|
6867
|
+
writeFileSync4(target, exportInputGroup(p, gp.id) + "\n", "utf8");
|
|
4749
6868
|
out(t("\u66F8\u304D\u51FA\u3057: {target}", { target }));
|
|
4750
6869
|
return;
|
|
4751
6870
|
}
|
|
4752
6871
|
case "group-import": {
|
|
4753
6872
|
if (!rest[0]) throw new Error(t("<path> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4754
|
-
const r = importInputGroup(p,
|
|
6873
|
+
const r = importInputGroup(p, readFileSync9(rest[0], "utf8"));
|
|
4755
6874
|
save(path, r.project);
|
|
4756
6875
|
out(t("\u8AAD\u307F\u8FBC\u307F: \u30B0\u30EB\u30FC\u30D7\u300C{name}\u300D", { name: String(inputGroupsOf(r.project).find((g) => g.id === r.groupId)?.name) }));
|
|
4757
6876
|
return;
|
|
@@ -4779,23 +6898,25 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4779
6898
|
case "merge": {
|
|
4780
6899
|
const [basePath, oursPath, theirsPath] = rest;
|
|
4781
6900
|
if (!basePath || !oursPath || !theirsPath) throw new Error(t("merge <base> <ours> <theirs> \u306E 3 \u3064\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4782
|
-
const
|
|
4783
|
-
const text2 =
|
|
4784
|
-
readRevisions.set(
|
|
6901
|
+
const read2 = (f) => {
|
|
6902
|
+
const text2 = readFileSync9(f, "utf8");
|
|
6903
|
+
readRevisions.set(resolve5(f), revisionOf(text2));
|
|
4785
6904
|
return text2.trim() ? fromJSON(text2) : null;
|
|
4786
6905
|
};
|
|
4787
|
-
const base =
|
|
4788
|
-
const ours =
|
|
4789
|
-
const theirs =
|
|
6906
|
+
const base = read2(basePath);
|
|
6907
|
+
const ours = read2(oursPath);
|
|
6908
|
+
const theirs = read2(theirsPath);
|
|
4790
6909
|
if (!ours || !theirs) throw new Error(t("ours / theirs \u304C\u8AAD\u3081\u307E\u305B\u3093"));
|
|
4791
6910
|
const r = mergeProjects(base, ours, theirs);
|
|
4792
6911
|
let q = r.project;
|
|
6912
|
+
const problem = projectProblem(q);
|
|
6913
|
+
if (problem) throw new Error(t("\u30DE\u30FC\u30B8\u3057\u305F\u7D50\u679C\u304C\u8A08\u753B\u3068\u3057\u3066\u6B63\u3057\u304F\u306A\u3044\u305F\u3081\u3001\u66F8\u304D\u8FBC\u307F\u307E\u305B\u3093\u3067\u3057\u305F (\u81EA\u5206\u306E\u5074\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u305D\u306E\u307E\u307E\u3067\u3059): {problem}", { problem }));
|
|
4793
6914
|
const kept = r.conflicts.filter((c) => !c.automatic && (c.ours === void 0 || c.theirs === void 0));
|
|
4794
6915
|
for (const c of r.conflicts) {
|
|
4795
6916
|
const message = kept.includes(c) ? t("\u30DE\u30FC\u30B8\u3067\u7247\u65B9\u304C\u524A\u9664\u30FB\u7247\u65B9\u304C\u5909\u66F4: {path} (\u5909\u66F4\u3055\u308C\u305F\u5074\u3092\u6B8B\u3057\u307E\u3057\u305F\u3002\u524A\u9664\u3057\u305F\u5074: {side})", { path: c.path, side: c.ours === void 0 ? t("\u81EA\u5206") : t("\u76F8\u624B") }) : t("\u30DE\u30FC\u30B8\u3067\u4E21\u5074\u304C\u5909\u66F4: {path} (\u63A1\u7528: {ours} / \u76F8\u624B: {theirs})", { path: c.path, ours: JSON.stringify(c.ours), theirs: JSON.stringify(c.theirs) });
|
|
4796
6917
|
q = { ...q, log: [...q.log, { id: `m${Math.random().toString(36).slice(2, 10)}`, at: (/* @__PURE__ */ new Date()).toISOString(), actor: "merge", kind: "note", message }] };
|
|
4797
6918
|
}
|
|
4798
|
-
commitFile(oursPath, toJSON(q) + "\n", readRevisions.get(
|
|
6919
|
+
commitFile(oursPath, toJSON(q) + "\n", readRevisions.get(resolve5(oursPath)));
|
|
4799
6920
|
const both = r.conflicts.length - kept.length;
|
|
4800
6921
|
out(
|
|
4801
6922
|
t("\u30DE\u30FC\u30B8: \u76F8\u624B\u306E\u5909\u66F4 {count} \u4EF6\u3092\u53D6\u308A\u8FBC\u307F", { count: r.merged }) + (both ? t("\u3001\u4E21\u5074\u3067\u5909\u66F4\u304C {count} \u4EF6 (\u81EA\u5206\u306E\u5024\u3092\u63A1\u7528\u3057\u3001\u76F8\u624B\u306E\u5024\u306F\u30ED\u30B0\u306B\u8A18\u9332)", { count: both }) : "") + (kept.length ? t("\u3001\u524A\u9664\u3068\u5909\u66F4\u306E\u7AF6\u5408\u304C {count} \u4EF6 (\u5909\u66F4\u3055\u308C\u305F\u5074\u3092\u6B8B\u3057\u3001\u30ED\u30B0\u306B\u8A18\u9332)", { count: kept.length }) : "")
|
|
@@ -4804,11 +6925,11 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4804
6925
|
return;
|
|
4805
6926
|
}
|
|
4806
6927
|
case "git-setup": {
|
|
4807
|
-
const root =
|
|
4808
|
-
const attrs =
|
|
6928
|
+
const root = dirname6(path);
|
|
6929
|
+
const attrs = join7(root, ".gitattributes");
|
|
4809
6930
|
const line = "boxglow.json merge=boxglow";
|
|
4810
|
-
const cur =
|
|
4811
|
-
if (!cur.split(/\r?\n/).includes(line))
|
|
6931
|
+
const cur = existsSync9(attrs) ? readFileSync9(attrs, "utf8") : "";
|
|
6932
|
+
if (!cur.split(/\r?\n/).includes(line)) writeFileSync4(attrs, (cur && !cur.endsWith("\n") ? cur + "\n" : cur) + line + "\n", "utf8");
|
|
4812
6933
|
execFileSync2("git", ["config", "merge.boxglow.name", "Boxglow plan merge (box level)"], { cwd: root });
|
|
4813
6934
|
execFileSync2("git", ["config", "merge.boxglow.driver", "npx boxglow merge %O %A %B"], { cwd: root });
|
|
4814
6935
|
out(t('\u767B\u9332\u3057\u307E\u3057\u305F: {attrs} \u306B\u300C{line}\u300D\u3001git config merge.boxglow.driver = "npx boxglow merge %O %A %B"\n\u4EE5\u5F8C git merge / pull / rebase \u3067 boxglow.json \u306F\u30DC\u30C3\u30AF\u30B9\u306E\u5358\u4F4D\u3067\u81EA\u52D5\u30DE\u30FC\u30B8\u3055\u308C\u307E\u3059 (\u30C1\u30FC\u30E0\u5168\u54E1\u304C\u3053\u306E\u30B3\u30DE\u30F3\u30C9\u3092 1 \u56DE\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044)', { attrs, line }));
|
|
@@ -4860,7 +6981,7 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
4860
6981
|
case "split": {
|
|
4861
6982
|
const b = mustFind(p, rest[0]);
|
|
4862
6983
|
p = ackDecisions(p, b.id, actor);
|
|
4863
|
-
const specText = str(options["spec-file"]) ?
|
|
6984
|
+
const specText = str(options["spec-file"]) ? readFileSync9(str(options["spec-file"]), "utf8") : str(options.spec);
|
|
4864
6985
|
if (!specText) throw new Error(t("--spec '<JSON>' \u307E\u305F\u306F --spec-file <path> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
4865
6986
|
const spec = JSON.parse(specText);
|
|
4866
6987
|
const r = splitBlock(p, b.id, spec, actor);
|
|
@@ -5036,7 +7157,7 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
5036
7157
|
for (const a of port2.artifacts) {
|
|
5037
7158
|
const owner = p.blocks[port2.blockId]?.title ?? "?";
|
|
5038
7159
|
if (a.kind === "file" && a.path) {
|
|
5039
|
-
const ref = findGitRef(a.path, [
|
|
7160
|
+
const ref = findGitRef(a.path, [dirname6(path), process.cwd()]);
|
|
5040
7161
|
if (ref) {
|
|
5041
7162
|
a.kind = "git";
|
|
5042
7163
|
a.repo = ref.repo;
|
|
@@ -5053,7 +7174,7 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
5053
7174
|
}
|
|
5054
7175
|
if (a.kind !== "git") continue;
|
|
5055
7176
|
if (!a.commit && a.path) {
|
|
5056
|
-
const ref = gitRefFor(
|
|
7177
|
+
const ref = gitRefFor(resolve5(dirname6(path), a.path));
|
|
5057
7178
|
if (ref?.commit) {
|
|
5058
7179
|
a.commit = ref.commit;
|
|
5059
7180
|
a.blob = ref.blob;
|
|
@@ -5065,7 +7186,7 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
5065
7186
|
continue;
|
|
5066
7187
|
}
|
|
5067
7188
|
}
|
|
5068
|
-
const r = checkGitRef(a,
|
|
7189
|
+
const r = checkGitRef(a, dirname6(path));
|
|
5069
7190
|
if (r.state === "moved" && r.path) {
|
|
5070
7191
|
lines.push(t("- [\u79FB\u52D5] {owner}.{port} \u300C{title}\u300D {from} -> {to}", { owner, port: port2.name, title: a.title, from: String(a.path), to: r.path }));
|
|
5071
7192
|
a.path = r.path;
|
|
@@ -5192,13 +7313,81 @@ function runCommand(cmd, rest, options, actor, path, p, AGENTS_SNIPPET, SKILL_MD
|
|
|
5192
7313
|
var argv = process.argv.slice(2);
|
|
5193
7314
|
if (argv[0] === "mcp") {
|
|
5194
7315
|
const { options } = parseArgs(argv.slice(1));
|
|
5195
|
-
if (str(options.file)) process.env.BOXGLOW_FILE =
|
|
7316
|
+
if (str(options.file)) process.env.BOXGLOW_FILE = resolve5(str(options.file));
|
|
5196
7317
|
if (str(options.actor)) process.env.BOXGLOW_ACTOR = str(options.actor);
|
|
5197
7318
|
if (str(options.lang)) process.env.BOXGLOW_LANG = str(options.lang);
|
|
5198
7319
|
startMcp(runCli).catch((e) => {
|
|
5199
7320
|
console.error(`[boxglow mcp] ${e instanceof Error ? e.message : String(e)}`);
|
|
5200
7321
|
process.exitCode = 1;
|
|
5201
7322
|
});
|
|
7323
|
+
} else if (argv[0] === "login" || argv[0] === "logout" || argv[0] === "whoami") {
|
|
7324
|
+
const { options } = parseArgs(argv.slice(1));
|
|
7325
|
+
(async () => {
|
|
7326
|
+
setLang(explicitLang(options) ?? "ja");
|
|
7327
|
+
const known = /* @__PURE__ */ new Set(["server", "lang", "file", "name"]);
|
|
7328
|
+
const unknown = Object.keys(options).filter((k) => !known.has(k));
|
|
7329
|
+
if (unknown.length > 0) {
|
|
7330
|
+
console.log(t("boxglow {cmd} \u304C\u77E5\u3089\u306A\u3044\u6307\u5B9A\u3067\u3059: {list}", { cmd: argv[0], list: unknown.map((k) => "--" + k).join(", ") }));
|
|
7331
|
+
process.exitCode = 1;
|
|
7332
|
+
return;
|
|
7333
|
+
}
|
|
7334
|
+
let server = str(options.server) ?? process.env.BOXGLOW_SERVER;
|
|
7335
|
+
if (!server) {
|
|
7336
|
+
try {
|
|
7337
|
+
const bound = bindingsOf(locateFile(str(options.file))).bindings;
|
|
7338
|
+
if (bound.length === 1) server = bound[0].server;
|
|
7339
|
+
} catch {
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
if (!server) {
|
|
7343
|
+
console.log(t("\u30B5\u30FC\u30D0\u30FC\u304C\u6C7A\u307E\u3063\u3066\u3044\u307E\u305B\u3093\u3002--server <URL> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"));
|
|
7344
|
+
process.exitCode = 1;
|
|
7345
|
+
return;
|
|
7346
|
+
}
|
|
7347
|
+
const out2 = (text2) => console.log(text2);
|
|
7348
|
+
process.exitCode = argv[0] === "login" ? await runLogin({ server, out: out2, deviceName: str(options.name) }) : argv[0] === "logout" ? await runLogout({ server, out: out2 }) : await runWhoami({ server, out: out2 });
|
|
7349
|
+
})().catch((e) => {
|
|
7350
|
+
console.error(`[boxglow ${argv[0]}] ${e instanceof Error ? e.message : String(e)}`);
|
|
7351
|
+
process.exitCode = 1;
|
|
7352
|
+
});
|
|
7353
|
+
} else if (argv[0] === "sync") {
|
|
7354
|
+
const { options } = parseArgs(argv.slice(1));
|
|
7355
|
+
process.stdout.on("error", (e) => {
|
|
7356
|
+
if (e.code === "EPIPE") process.exit(process.exitCode ?? 0);
|
|
7357
|
+
else throw e;
|
|
7358
|
+
});
|
|
7359
|
+
(async () => {
|
|
7360
|
+
setLang(explicitLang(options) ?? "ja");
|
|
7361
|
+
const file = locateFile(str(options.file));
|
|
7362
|
+
try {
|
|
7363
|
+
setLang(explicitLang(options) ?? load(file).lang ?? "ja");
|
|
7364
|
+
} catch {
|
|
7365
|
+
}
|
|
7366
|
+
const known = /* @__PURE__ */ new Set(["file", "lang", "actor", "watch", "server", "project", "adopt", "restore", "resolve", "link", "prefer", "recover", "applied", "not-applied", "account"]);
|
|
7367
|
+
const unknown = Object.keys(options).filter((k) => !known.has(k));
|
|
7368
|
+
if (unknown.length > 0) {
|
|
7369
|
+
out(t("boxglow sync \u304C\u77E5\u3089\u306A\u3044\u6307\u5B9A\u3067\u3059: {list}", { list: unknown.map((k) => "--" + k).join(", ") }));
|
|
7370
|
+
process.exitCode = 1;
|
|
7371
|
+
return;
|
|
7372
|
+
}
|
|
7373
|
+
process.exitCode = await (options.watch ? runWatchCommand : runSyncCommand)({
|
|
7374
|
+
file,
|
|
7375
|
+
server: str(options.server),
|
|
7376
|
+
project: str(options.project),
|
|
7377
|
+
adopt: str(options.adopt),
|
|
7378
|
+
restore: str(options.restore),
|
|
7379
|
+
resolve: str(options.resolve),
|
|
7380
|
+
link: str(options.link),
|
|
7381
|
+
prefer: str(options.prefer),
|
|
7382
|
+
recover: str(options.recover),
|
|
7383
|
+
applied: !!options.applied,
|
|
7384
|
+
notApplied: !!options["not-applied"],
|
|
7385
|
+
account: str(options.account)
|
|
7386
|
+
}, out);
|
|
7387
|
+
})().catch((e) => {
|
|
7388
|
+
console.error(`[boxglow sync] ${e instanceof Error ? e.message : String(e)}`);
|
|
7389
|
+
process.exitCode = 1;
|
|
7390
|
+
});
|
|
5202
7391
|
} else if (argv[0] === "serve") {
|
|
5203
7392
|
const { options } = parseArgs(argv.slice(1));
|
|
5204
7393
|
try {
|