@oevery/rsp 3.1.0-beta.4 → 3.1.1
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 +45 -346
- package/README.zh-CN.md +46 -335
- package/dist/{chunk-D7XDXBFO.mjs → chunk-3Z4IO2TD.mjs} +1 -1
- package/dist/{chunk-FI3SYHVH.mjs → chunk-MUNLLVVH.mjs} +141 -6
- package/dist/{chunk-FJVH7ZYU.mjs → chunk-WJXKLLIF.mjs} +15 -7
- package/dist/cli.mjs +242 -31
- package/dist/{entry-FVUG6YW2.mjs → entry-IPHJBI53.mjs} +2 -2
- package/dist/{entry-5OPKUZHF.mjs → entry-KAWA2AS4.mjs} +2 -2
- package/package.json +10 -3
- package/rules/rsp-rules.md +4 -2
- package/skills/rsp/SKILL.md +5 -15
- package/skills/rsp/references/groups-dependencies.md +1 -0
- package/skills/rsp/references/managed-routing.md +7 -22
- package/skills/rsp/references/release-operations.md +9 -0
- package/skills/rsp/references/reopen-recovery.md +9 -0
- package/skills/rsp-commit/SKILL.md +5 -1
- package/skills/rsp-implement/SKILL.md +5 -5
- package/skills/rsp-manage/SKILL.md +10 -4
- package/skills/rsp-shape/SKILL.md +2 -0
- package/skills/rsp-shape/references/external-issue-input.md +9 -0
package/dist/cli.mjs
CHANGED
|
@@ -4,14 +4,18 @@ import {
|
|
|
4
4
|
installPackagedSkills,
|
|
5
5
|
printPackagedSkillInventory,
|
|
6
6
|
printSkillInstallResult
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-3Z4IO2TD.mjs";
|
|
8
8
|
import {
|
|
9
9
|
ArchiveHistoryError,
|
|
10
10
|
GROUP_BRIEF_FILENAME,
|
|
11
|
+
GROUP_REOPEN_COMPLETION_PREFIX,
|
|
11
12
|
HISTORY_DEFAULT_LIMIT,
|
|
12
13
|
HISTORY_MAX_LIMIT,
|
|
14
|
+
IssueRelationshipError,
|
|
13
15
|
WorkRefError,
|
|
16
|
+
createIssueRelationship,
|
|
14
17
|
generateGroupBriefContent,
|
|
18
|
+
getGroupReopenEvidence,
|
|
15
19
|
hasArchivedGroupBrief,
|
|
16
20
|
historyInspectionComplete,
|
|
17
21
|
inspectArchiveHistory,
|
|
@@ -23,6 +27,7 @@ import {
|
|
|
23
27
|
inspectWorkTree,
|
|
24
28
|
normalizeExecutableWorkRef,
|
|
25
29
|
normalizeWorkRefSegment,
|
|
30
|
+
parseIssueRelationships,
|
|
26
31
|
projectDependencyForest,
|
|
27
32
|
queryArchiveHistory,
|
|
28
33
|
readArchiveHistoryDetail,
|
|
@@ -30,8 +35,9 @@ import {
|
|
|
30
35
|
resolveExecutableChange,
|
|
31
36
|
resolveFocusMarkerPath,
|
|
32
37
|
resolveWorkRef,
|
|
33
|
-
selectArchiveHistoryRecord
|
|
34
|
-
|
|
38
|
+
selectArchiveHistoryRecord,
|
|
39
|
+
selectArchivedGroupBrief
|
|
40
|
+
} from "./chunk-MUNLLVVH.mjs";
|
|
35
41
|
import {
|
|
36
42
|
ARCHIVES_DIR,
|
|
37
43
|
CHANGES_DIR,
|
|
@@ -40,7 +46,9 @@ import {
|
|
|
40
46
|
DEFAULT_DECISION_RECORDS_PATH,
|
|
41
47
|
DocumentSectionCardinalityError,
|
|
42
48
|
FOCUS_DIR,
|
|
49
|
+
GROUP_BRIEF_DOCUMENT_SCHEMA,
|
|
43
50
|
LOCK_PATH,
|
|
51
|
+
ManagedPathError,
|
|
44
52
|
OBSOLETE_RSP_RULES_PATH,
|
|
45
53
|
PKG_ROOT,
|
|
46
54
|
RSP_DIR,
|
|
@@ -61,8 +69,10 @@ import {
|
|
|
61
69
|
generateChangeContent,
|
|
62
70
|
generateDesignContent,
|
|
63
71
|
generateSpecContent,
|
|
72
|
+
getDocumentSectionBody,
|
|
64
73
|
getDocumentSectionDefinitionByHeading,
|
|
65
74
|
getDocumentSections,
|
|
75
|
+
getDocumentTitles,
|
|
66
76
|
getDurableReviewCandidateTargets,
|
|
67
77
|
getVersion,
|
|
68
78
|
guardRspInitialized,
|
|
@@ -91,7 +101,7 @@ import {
|
|
|
91
101
|
validateDecisionRecordsFilesystemPath,
|
|
92
102
|
walkMarkdownFiles,
|
|
93
103
|
writeManagedFile
|
|
94
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-WJXKLLIF.mjs";
|
|
95
105
|
|
|
96
106
|
// src/cli.ts
|
|
97
107
|
import { resolve } from "path";
|
|
@@ -734,6 +744,17 @@ async function runCheck(options = {}) {
|
|
|
734
744
|
}
|
|
735
745
|
}
|
|
736
746
|
if (fm) {
|
|
747
|
+
try {
|
|
748
|
+
parseIssueRelationships(fm);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
addDiagnostic({
|
|
751
|
+
severity: "error",
|
|
752
|
+
code: error instanceof IssueRelationshipError ? error.code : "invalid_issue_metadata",
|
|
753
|
+
change: name,
|
|
754
|
+
path: fp,
|
|
755
|
+
message: error instanceof Error ? error.message : String(error)
|
|
756
|
+
});
|
|
757
|
+
}
|
|
737
758
|
if (!("kind" in fm)) {
|
|
738
759
|
addDiagnostic({
|
|
739
760
|
severity: "error",
|
|
@@ -922,15 +943,21 @@ async function createChange(name, summary = "", kind, options = {}) {
|
|
|
922
943
|
console.error(` ${pc.red("Usage:")} rsp create <name> [summary]`);
|
|
923
944
|
process.exit(1);
|
|
924
945
|
}
|
|
946
|
+
const hasIssueOption = options.issue !== void 0;
|
|
947
|
+
if (options.issueRelation && !hasIssueOption)
|
|
948
|
+
throw new Error("--issue-relation requires --issue");
|
|
949
|
+
const issues = options.issue !== void 0 ? [createIssueRelationship(options.issue, options.issueRelation)] : [];
|
|
925
950
|
guardRspInitialized();
|
|
926
951
|
try {
|
|
927
952
|
return await withRspLock("create-change", async () => {
|
|
928
953
|
const workRef = await resolveExecutableChange(name);
|
|
929
954
|
const changePath = workRef.path;
|
|
930
955
|
const existed = existsSync2(changePath);
|
|
956
|
+
if (existed && hasIssueOption)
|
|
957
|
+
throw new Error(`Change already exists; --issue cannot update ${workRef.name}`);
|
|
931
958
|
if (!existed) {
|
|
932
959
|
const focusEntry = resolveFocusMarkerPath(workRef);
|
|
933
|
-
const content = generateChangeContent(workRef.name, summary, kind, { lite: Boolean(options.lite) });
|
|
960
|
+
const content = generateChangeContent(workRef.name, summary, kind, { lite: Boolean(options.lite), issues });
|
|
934
961
|
await mkdir4(dirname2(changePath), { recursive: true });
|
|
935
962
|
let changeCreated = false;
|
|
936
963
|
try {
|
|
@@ -1631,7 +1658,9 @@ function exitWorkRefError(error) {
|
|
|
1631
1658
|
}
|
|
1632
1659
|
|
|
1633
1660
|
// src/commands/group.ts
|
|
1634
|
-
import {
|
|
1661
|
+
import { randomUUID } from "crypto";
|
|
1662
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1663
|
+
import { link, mkdir as mkdir7, readdir as readdir2, readFile as readFile8, rename as rename2, unlink as unlink8, writeFile as writeFile4 } from "fs/promises";
|
|
1635
1664
|
import { join as join7 } from "path";
|
|
1636
1665
|
var ChangeGroupError = class extends Error {
|
|
1637
1666
|
name = "ChangeGroupError";
|
|
@@ -1719,6 +1748,136 @@ async function closeChangeGroup(name) {
|
|
|
1719
1748
|
throw error;
|
|
1720
1749
|
}
|
|
1721
1750
|
}
|
|
1751
|
+
async function reopenChangeGroup(name, options) {
|
|
1752
|
+
if (!name) {
|
|
1753
|
+
console.error(` ${pc.red("Usage:")} rsp group reopen <name> --reason <text> [--from <archive-path>]`);
|
|
1754
|
+
process.exit(1);
|
|
1755
|
+
}
|
|
1756
|
+
const reason = options.reason?.trim();
|
|
1757
|
+
if (!reason || /[\r\n]/.test(reason)) {
|
|
1758
|
+
console.error(` ${pc.red("Error:")} --reason must be one non-empty line`);
|
|
1759
|
+
process.exit(1);
|
|
1760
|
+
}
|
|
1761
|
+
guardRspInitialized();
|
|
1762
|
+
try {
|
|
1763
|
+
await withRspLock("reopen-change-group", async () => {
|
|
1764
|
+
const groupName = normalizeWorkRefSegment(name);
|
|
1765
|
+
await requireEmptyGroupRecoverySubtree(join7(CHANGES_DIR, groupName), `Change Group work subtree for ${groupName}`);
|
|
1766
|
+
await requireEmptyGroupRecoverySubtree(join7(FOCUS_DIR, groupName), `Change Group focus subtree for ${groupName}`);
|
|
1767
|
+
const ref = resolveWorkRef(`${groupName}/brief`);
|
|
1768
|
+
if (ref.kind !== "group-brief")
|
|
1769
|
+
throw new ChangeGroupError(`invalid Change Group name: ${name}`);
|
|
1770
|
+
const inspection = await inspectArchiveHistory();
|
|
1771
|
+
if (!historyInspectionComplete(inspection)) {
|
|
1772
|
+
throw new ArchiveHistoryError(
|
|
1773
|
+
"archive_inspection_incomplete",
|
|
1774
|
+
"archive history inspection is incomplete; resolve the reported diagnostics before reopening a Change Group"
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
const record = selectArchivedGroupBrief(
|
|
1778
|
+
inspection.groupBriefs,
|
|
1779
|
+
options.from ? { path: options.from } : { group: groupName }
|
|
1780
|
+
);
|
|
1781
|
+
if (record.group !== groupName) {
|
|
1782
|
+
throw new ArchiveHistoryError(
|
|
1783
|
+
"archive_identity_mismatch",
|
|
1784
|
+
`selected archived Change Group belongs to ${record.group}, not ${groupName}`
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
if (existsSync6(ref.path))
|
|
1788
|
+
throw new ArchiveHistoryError("open_group_exists", `open Change Group already exists: ${groupName}`);
|
|
1789
|
+
const archivedContent = await readFile8(record.sourcePath, "utf-8");
|
|
1790
|
+
const document = parseRspDocument(archivedContent, GROUP_BRIEF_DOCUMENT_SCHEMA);
|
|
1791
|
+
validateArchivedGroupBrief(document, groupName);
|
|
1792
|
+
const evidenceKey = `${GROUP_REOPEN_COMPLETION_PREFIX} \`${record.path}\`: ${reason}`;
|
|
1793
|
+
const retainedEvidence = /* @__PURE__ */ new Set();
|
|
1794
|
+
for (const retained of inspection.groupBriefs.filter((candidate) => candidate.group === groupName)) {
|
|
1795
|
+
const content = retained.sourcePath === record.sourcePath ? archivedContent : await readFile8(retained.sourcePath, "utf-8");
|
|
1796
|
+
for (const item of getGroupReopenEvidence(content))
|
|
1797
|
+
retainedEvidence.add(item.key);
|
|
1798
|
+
}
|
|
1799
|
+
if (retainedEvidence.has(evidenceKey)) {
|
|
1800
|
+
throw new ArchiveHistoryError(
|
|
1801
|
+
"archive_reopen_evidence_replayed",
|
|
1802
|
+
`reopen evidence already exists in retained Change Group history; choose a fresh reason or a different exact archive: ${record.path}`
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
const reopenedContent = appendDocumentSectionItem(
|
|
1806
|
+
document,
|
|
1807
|
+
"completionConditions",
|
|
1808
|
+
`- [ ] ${evidenceKey}`
|
|
1809
|
+
);
|
|
1810
|
+
const groupDirectory = resolveManagedDirectoryChain(CHANGES_DIR, [groupName], "Change Group path");
|
|
1811
|
+
await mkdir7(groupDirectory, { recursive: true });
|
|
1812
|
+
const temporaryPath = join7(RSP_DIR, `.group-reopen-${process.pid}-${randomUUID()}.tmp`);
|
|
1813
|
+
let mutationError;
|
|
1814
|
+
try {
|
|
1815
|
+
await writeFile4(temporaryPath, reopenedContent, { flag: "wx" });
|
|
1816
|
+
await link(temporaryPath, ref.path);
|
|
1817
|
+
} catch (error) {
|
|
1818
|
+
mutationError = error;
|
|
1819
|
+
}
|
|
1820
|
+
try {
|
|
1821
|
+
await unlink8(temporaryPath);
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
if (!mutationError)
|
|
1824
|
+
console.log(` ${pc.yellow("\u26A0")} temporary reopen file cleanup failed: ${toErrorMessage(error)}`);
|
|
1825
|
+
}
|
|
1826
|
+
if (mutationError) {
|
|
1827
|
+
try {
|
|
1828
|
+
await cleanupEmptyParentDirs(ref.path, CHANGES_DIR);
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
if (mutationError.code === "EEXIST")
|
|
1832
|
+
throw new ArchiveHistoryError("open_group_exists", `open Change Group already exists: ${groupName}`);
|
|
1833
|
+
throw mutationError;
|
|
1834
|
+
}
|
|
1835
|
+
console.log(` ${pc.green("Reopened Change Group:")} ${groupName}`);
|
|
1836
|
+
console.log(` ${pc.dim("retained archive")} \u2192 ${record.path}`);
|
|
1837
|
+
console.log(` ${pc.dim("unfocused Group Brief")} \u2192 ${ref.path}`);
|
|
1838
|
+
console.log(` ${pc.cyan("Next:")} run rsp reopen ${groupName}/<change> --reason <text> only for an incomplete archived child
|
|
1839
|
+
`);
|
|
1840
|
+
});
|
|
1841
|
+
} catch (error) {
|
|
1842
|
+
if (error instanceof ArchiveHistoryError || error instanceof WorkRefError || error instanceof ChangeGroupError || error instanceof ManagedPathError) {
|
|
1843
|
+
console.error(` ${pc.red("Error:")} ${error.message}`);
|
|
1844
|
+
if (error instanceof ArchiveHistoryError) {
|
|
1845
|
+
for (const candidate of error.candidates)
|
|
1846
|
+
console.error(` ${candidate}`);
|
|
1847
|
+
if (error.candidatesTruncated)
|
|
1848
|
+
console.error(` ... ${error.candidateTotal - error.candidates.length} more`);
|
|
1849
|
+
}
|
|
1850
|
+
process.exit(1);
|
|
1851
|
+
}
|
|
1852
|
+
throw error;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
async function requireEmptyGroupRecoverySubtree(path, label) {
|
|
1856
|
+
if (!requireManagedDirectory(path, label, { allowMissing: true }))
|
|
1857
|
+
return;
|
|
1858
|
+
let entries;
|
|
1859
|
+
try {
|
|
1860
|
+
entries = await readdir2(path);
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
throw new ChangeGroupError(`unable to inspect ${label}: ${toErrorMessage(error)}`);
|
|
1863
|
+
}
|
|
1864
|
+
if (entries.length > 0)
|
|
1865
|
+
throw new ChangeGroupError(`${label} must be absent or empty before reopen; found: ${entries.sort().join(", ")}`);
|
|
1866
|
+
}
|
|
1867
|
+
function validateArchivedGroupBrief(document, group) {
|
|
1868
|
+
const titles = getDocumentTitles(document, "identity");
|
|
1869
|
+
if (titles.length !== 1 || titles[0] !== group)
|
|
1870
|
+
throw new ArchiveHistoryError("archive_heading_invalid", `archived Change Group must contain exactly one canonical identity heading for ${group}`);
|
|
1871
|
+
for (const section of GROUP_BRIEF_DOCUMENT_SCHEMA.sections) {
|
|
1872
|
+
const matches = getDocumentSections(document, section.id);
|
|
1873
|
+
if (matches.length !== 1 || !matches[0].canonical || !getDocumentSectionBody(document, section.id)) {
|
|
1874
|
+
throw new ArchiveHistoryError(
|
|
1875
|
+
"archive_section_invalid",
|
|
1876
|
+
`archived Change Group must contain exactly one canonical required section: ${section.heading}`
|
|
1877
|
+
);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1722
1881
|
function collectCloseReasons(group, errors) {
|
|
1723
1882
|
const reasons = [...errors, ...group.warnings];
|
|
1724
1883
|
const open = group.slices.filter((slice) => slice.state === "open").map((slice) => slice.name);
|
|
@@ -1926,6 +2085,11 @@ function printHistoryDetail(result) {
|
|
|
1926
2085
|
console.log(` ${pc.dim("Kind:")} ${record.kind}`);
|
|
1927
2086
|
console.log(` ${pc.dim("Path:")} ${record.path}`);
|
|
1928
2087
|
console.log(` ${pc.dim("Summary:")} ${record.summary}${record.summaryTruncated ? "\u2026" : ""}`);
|
|
2088
|
+
if ((record.issues?.length ?? 0) > 0) {
|
|
2089
|
+
console.log(` ${pc.dim("Issues:")}`);
|
|
2090
|
+
for (const issue of record.issues ?? [])
|
|
2091
|
+
console.log(` ${issue.relation} ${issue.url}`);
|
|
2092
|
+
}
|
|
1929
2093
|
console.log(` ${pc.dim("Scenarios:")} ${record.scenarioCount}`);
|
|
1930
2094
|
console.log(` ${pc.dim("Tasks:")} ${record.checkboxes.tasks.done}/${record.checkboxes.tasks.total}`);
|
|
1931
2095
|
console.log(` ${pc.dim("Verify:")} ${record.checkboxes.verify.done}/${record.checkboxes.verify.total}`);
|
|
@@ -1943,8 +2107,8 @@ function printHistoryDetail(result) {
|
|
|
1943
2107
|
}
|
|
1944
2108
|
|
|
1945
2109
|
// src/commands/init.ts
|
|
1946
|
-
import { existsSync as
|
|
1947
|
-
import { mkdir as mkdir8, readFile as
|
|
2110
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2111
|
+
import { mkdir as mkdir8, readFile as readFile9 } from "fs/promises";
|
|
1948
2112
|
import { join as join8 } from "path";
|
|
1949
2113
|
function generateConfigTemplate() {
|
|
1950
2114
|
const fmtList = (items, indent) => items.map((i) => `${" ".repeat(indent)}# - ${i}`).join("\n");
|
|
@@ -1986,7 +2150,7 @@ function toTitle(projectName) {
|
|
|
1986
2150
|
return projectName.startsWith("@") ? projectName.split("/")[1] || projectName : projectName;
|
|
1987
2151
|
}
|
|
1988
2152
|
async function initProject(args = {}) {
|
|
1989
|
-
const isNew = !
|
|
2153
|
+
const isNew = !existsSync7(RSP_DIR);
|
|
1990
2154
|
const projectName = await detectProjectName();
|
|
1991
2155
|
const mode = args.agentsMode ?? "managed";
|
|
1992
2156
|
if (mode !== "managed" && mode !== "print") {
|
|
@@ -2001,7 +2165,7 @@ async function initProject(args = {}) {
|
|
|
2001
2165
|
const agentsInspection = inspectManagedFile(agentsPath, "AGENTS.md", { allowMissing: true });
|
|
2002
2166
|
if (agentsInspection.issue)
|
|
2003
2167
|
throw agentsInspection.issue;
|
|
2004
|
-
if (
|
|
2168
|
+
if (existsSync7(CONFIG_PATH)) {
|
|
2005
2169
|
clearConfigCache();
|
|
2006
2170
|
const existingConfig = await inspectRspConfig();
|
|
2007
2171
|
if (existingConfig.issues.length > 0)
|
|
@@ -2022,7 +2186,7 @@ async function initProject(args = {}) {
|
|
|
2022
2186
|
for (const directory of dirs)
|
|
2023
2187
|
await mkdir8(directory.path, { recursive: true });
|
|
2024
2188
|
let created = false;
|
|
2025
|
-
const bundledRules = await
|
|
2189
|
+
const bundledRules = await readFile9(join8(PKG_ROOT, "rules", "rsp-rules.md"), "utf-8");
|
|
2026
2190
|
created = await ensureFile(RSP_RULES_PATH, bundledRules) || created;
|
|
2027
2191
|
created = await ensureFile(join8(RSP_DIR, "config.yaml"), generateConfigTemplate()) || created;
|
|
2028
2192
|
clearConfigCache();
|
|
@@ -2055,7 +2219,7 @@ ${upsertRspAgentsBlock("").content}
|
|
|
2055
2219
|
created = true;
|
|
2056
2220
|
agentsUpdated = true;
|
|
2057
2221
|
} else {
|
|
2058
|
-
const existing = await
|
|
2222
|
+
const existing = await readFile9(agentsPath, "utf-8");
|
|
2059
2223
|
const next = upsertRspAgentsBlock(existing);
|
|
2060
2224
|
if (next.changed) {
|
|
2061
2225
|
await writeManagedFile(agentsPath, next.content, "AGENTS.md");
|
|
@@ -2064,7 +2228,7 @@ ${upsertRspAgentsBlock("").content}
|
|
|
2064
2228
|
}
|
|
2065
2229
|
}
|
|
2066
2230
|
if (mode === "print") {
|
|
2067
|
-
const managed = await
|
|
2231
|
+
const managed = await readFile9(agentsPath, "utf-8");
|
|
2068
2232
|
console.log(`
|
|
2069
2233
|
${managed}
|
|
2070
2234
|
`);
|
|
@@ -2103,7 +2267,7 @@ ${managed}
|
|
|
2103
2267
|
}
|
|
2104
2268
|
|
|
2105
2269
|
// src/commands/ready.ts
|
|
2106
|
-
import { readFile as
|
|
2270
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2107
2271
|
function exitReadyError(name, error, options) {
|
|
2108
2272
|
if (options.json) {
|
|
2109
2273
|
emitJson({
|
|
@@ -2138,7 +2302,7 @@ async function showReady(name, options = {}) {
|
|
|
2138
2302
|
const runtime = [];
|
|
2139
2303
|
let content;
|
|
2140
2304
|
try {
|
|
2141
|
-
content = await
|
|
2305
|
+
content = await readFile10(srcPath, "utf-8");
|
|
2142
2306
|
} catch {
|
|
2143
2307
|
console.error(` ${pc.red("Error:")} unable to read .rsp/changes/${name}.md`);
|
|
2144
2308
|
process.exit(1);
|
|
@@ -2221,8 +2385,8 @@ function formatArchiveReady(value) {
|
|
|
2221
2385
|
}
|
|
2222
2386
|
|
|
2223
2387
|
// src/commands/reopen.ts
|
|
2224
|
-
import { existsSync as
|
|
2225
|
-
import { mkdir as mkdir9, readFile as
|
|
2388
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2389
|
+
import { mkdir as mkdir9, readFile as readFile11, unlink as unlink9, writeFile as writeFile5 } from "fs/promises";
|
|
2226
2390
|
import { dirname as dirname4 } from "path";
|
|
2227
2391
|
async function reopenChange(name, options) {
|
|
2228
2392
|
if (!name) {
|
|
@@ -2267,12 +2431,12 @@ async function reopenChange(name, options) {
|
|
|
2267
2431
|
}
|
|
2268
2432
|
throw error;
|
|
2269
2433
|
}
|
|
2270
|
-
if (
|
|
2434
|
+
if (existsSync8(workRef.path))
|
|
2271
2435
|
throw new ArchiveHistoryError("open_change_exists", `open Change already exists: ${name}`);
|
|
2272
2436
|
const focusEntry = resolveFocusMarkerPath(workRef);
|
|
2273
|
-
if (
|
|
2437
|
+
if (existsSync8(focusEntry))
|
|
2274
2438
|
throw new ArchiveHistoryError("focus_marker_exists", `focus marker already exists for non-open Change: ${name}; run rsp doctor`);
|
|
2275
|
-
const archivedContent = await
|
|
2439
|
+
const archivedContent = await readFile11(record.sourcePath, "utf-8");
|
|
2276
2440
|
const reopenedContent = appendChecklistItem(
|
|
2277
2441
|
appendChecklistItem(archivedContent, "tasks", `- [ ] Resolve reopened concern: ${reason}`),
|
|
2278
2442
|
"verify",
|
|
@@ -2289,7 +2453,7 @@ async function reopenChange(name, options) {
|
|
|
2289
2453
|
} catch (error) {
|
|
2290
2454
|
if (changeCreated) {
|
|
2291
2455
|
try {
|
|
2292
|
-
await
|
|
2456
|
+
await unlink9(workRef.path);
|
|
2293
2457
|
await cleanupEmptyParentDirs(workRef.path, CHANGES_DIR);
|
|
2294
2458
|
} catch {
|
|
2295
2459
|
}
|
|
@@ -2301,7 +2465,7 @@ async function reopenChange(name, options) {
|
|
|
2301
2465
|
console.log(` ${pc.dim("focused via focus.d")} \u2192 ${workRef.name}`);
|
|
2302
2466
|
console.log(` ${pc.cyan("Next:")} refine the reopened Task and Verify evidence, then resolve the concern
|
|
2303
2467
|
`);
|
|
2304
|
-
if (
|
|
2468
|
+
if (existsSync8(".git")) {
|
|
2305
2469
|
console.log(` ${pc.cyan("Git delivery:")}`);
|
|
2306
2470
|
console.log(` git status --short`);
|
|
2307
2471
|
console.log(` Inspect the complete reopen transition; stage and commit only with separate Git authority.`);
|
|
@@ -2337,7 +2501,7 @@ function appendChecklistItem(content, sectionId, item) {
|
|
|
2337
2501
|
}
|
|
2338
2502
|
|
|
2339
2503
|
// src/commands/show.ts
|
|
2340
|
-
import { readFile as
|
|
2504
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
2341
2505
|
function exitShowError(error, options) {
|
|
2342
2506
|
const nextActions = error.code === "no_focused_change" ? ["Run: rsp status", "Run: rsp focus <name>", "Or run: rsp create <name>"] : [];
|
|
2343
2507
|
if (options.json) {
|
|
@@ -2395,15 +2559,19 @@ async function showChange(nameOrFocused, options = {}) {
|
|
|
2395
2559
|
const srcPath = workRef.path;
|
|
2396
2560
|
let content;
|
|
2397
2561
|
try {
|
|
2398
|
-
content = await
|
|
2562
|
+
content = await readFile12(srcPath, "utf-8");
|
|
2399
2563
|
} catch {
|
|
2400
2564
|
exitShowError({ code: "change_read_failed", message: `unable to read .rsp/changes/${name}.md` }, options);
|
|
2401
2565
|
}
|
|
2402
2566
|
let kind = "\u2014";
|
|
2567
|
+
let issues = [];
|
|
2403
2568
|
try {
|
|
2404
2569
|
const fm = parseFrontmatter(content);
|
|
2405
2570
|
kind = fm?.kind ? String(fm.kind) : "\u2014";
|
|
2571
|
+
issues = parseIssueRelationships(fm);
|
|
2406
2572
|
} catch (error) {
|
|
2573
|
+
if (error instanceof IssueRelationshipError)
|
|
2574
|
+
exitShowError({ code: error.code, message: error.message }, options);
|
|
2407
2575
|
kind = "(invalid)";
|
|
2408
2576
|
reportRuntime({
|
|
2409
2577
|
code: "frontmatter_parse_failed",
|
|
@@ -2450,6 +2618,7 @@ async function showChange(nameOrFocused, options = {}) {
|
|
|
2450
2618
|
name,
|
|
2451
2619
|
path: normalizeLogicalPath(srcPath),
|
|
2452
2620
|
kind,
|
|
2621
|
+
issues,
|
|
2453
2622
|
isFocused,
|
|
2454
2623
|
progress: { done: cb.done, total: cb.total },
|
|
2455
2624
|
blockers,
|
|
@@ -2468,6 +2637,11 @@ async function showChange(nameOrFocused, options = {}) {
|
|
|
2468
2637
|
console.log(` ${pc.bold("Change:")} ${pc.cyan(name)}`);
|
|
2469
2638
|
console.log(` ${pc.dim("Path:")} ${normalizeLogicalPath(srcPath)}`);
|
|
2470
2639
|
console.log(` ${pc.dim("Kind:")} ${kind}`);
|
|
2640
|
+
if (issues.length > 0) {
|
|
2641
|
+
console.log(` ${pc.dim("Issues:")}`);
|
|
2642
|
+
for (const issue of issues)
|
|
2643
|
+
console.log(` ${issue.relation} ${issue.url}`);
|
|
2644
|
+
}
|
|
2471
2645
|
console.log(` ${pc.dim("Focused:")} ${isFocused ? pc.green("yes") : pc.dim("no")}`);
|
|
2472
2646
|
console.log(` ${pc.dim("Progress:")} ${cb.done}/${cb.total}`);
|
|
2473
2647
|
console.log(` ${pc.dim("Blockers:")} ${blockers ? pc.yellow("yes") : pc.green("no")}`);
|
|
@@ -2872,24 +3046,36 @@ var createCommand = defineCommand({
|
|
|
2872
3046
|
description: "Create .rsp/changes/<name>.md [summary]"
|
|
2873
3047
|
},
|
|
2874
3048
|
args: {
|
|
2875
|
-
name: {
|
|
3049
|
+
"name": {
|
|
2876
3050
|
type: "positional",
|
|
2877
3051
|
description: "Change name",
|
|
2878
3052
|
required: true
|
|
2879
3053
|
},
|
|
2880
|
-
kind: {
|
|
3054
|
+
"kind": {
|
|
2881
3055
|
type: "string",
|
|
2882
3056
|
description: "Optional kind for a kind-aware template (feature, fix, refactor, docs, ops, research)"
|
|
2883
3057
|
},
|
|
2884
|
-
lite: {
|
|
3058
|
+
"lite": {
|
|
2885
3059
|
type: "boolean",
|
|
2886
3060
|
description: "Use a shorter change template while keeping the required RSP sections",
|
|
2887
3061
|
default: false
|
|
3062
|
+
},
|
|
3063
|
+
"issue": {
|
|
3064
|
+
type: "string",
|
|
3065
|
+
description: "Attach one external issue URL without fetching it"
|
|
3066
|
+
},
|
|
3067
|
+
"issue-relation": {
|
|
3068
|
+
type: "string",
|
|
3069
|
+
description: "Issue relationship: relates (default) or closes; requires --issue"
|
|
2888
3070
|
}
|
|
2889
3071
|
},
|
|
2890
3072
|
async run({ args }) {
|
|
2891
3073
|
const summary = Array.isArray(args._) && args._.length > 1 ? args._.slice(1).join(" ") : "";
|
|
2892
|
-
await createChange(args.name, summary, args.kind, {
|
|
3074
|
+
await createChange(args.name, summary, args.kind, {
|
|
3075
|
+
lite: Boolean(args.lite),
|
|
3076
|
+
issue: args.issue,
|
|
3077
|
+
issueRelation: args.issueRelation
|
|
3078
|
+
});
|
|
2893
3079
|
}
|
|
2894
3080
|
});
|
|
2895
3081
|
var groupCreateCommand = defineCommand({
|
|
@@ -2912,7 +3098,7 @@ var groupCreateCommand = defineCommand({
|
|
|
2912
3098
|
var groupCommand = defineCommand({
|
|
2913
3099
|
meta: {
|
|
2914
3100
|
name: "group",
|
|
2915
|
-
description: "Create and
|
|
3101
|
+
description: "Create, close, and explicitly reopen shallow Change Groups"
|
|
2916
3102
|
},
|
|
2917
3103
|
subCommands: {
|
|
2918
3104
|
create: groupCreateCommand,
|
|
@@ -2931,6 +3117,31 @@ var groupCommand = defineCommand({
|
|
|
2931
3117
|
async run({ args }) {
|
|
2932
3118
|
await closeChangeGroup(args.name);
|
|
2933
3119
|
}
|
|
3120
|
+
}),
|
|
3121
|
+
reopen: defineCommand({
|
|
3122
|
+
meta: {
|
|
3123
|
+
name: "reopen",
|
|
3124
|
+
description: "Restore one archived Group Brief while retaining history"
|
|
3125
|
+
},
|
|
3126
|
+
args: {
|
|
3127
|
+
name: {
|
|
3128
|
+
type: "positional",
|
|
3129
|
+
description: "Archived Change Group name",
|
|
3130
|
+
required: true
|
|
3131
|
+
},
|
|
3132
|
+
from: {
|
|
3133
|
+
type: "string",
|
|
3134
|
+
description: "Exact .rsp/archives/... path when the Group has multiple archives"
|
|
3135
|
+
},
|
|
3136
|
+
reason: {
|
|
3137
|
+
type: "string",
|
|
3138
|
+
description: "One-line concern that makes Group completion unfinished",
|
|
3139
|
+
required: true
|
|
3140
|
+
}
|
|
3141
|
+
},
|
|
3142
|
+
async run({ args }) {
|
|
3143
|
+
await reopenChangeGroup(args.name, { from: args.from, reason: args.reason });
|
|
3144
|
+
}
|
|
2934
3145
|
})
|
|
2935
3146
|
}
|
|
2936
3147
|
});
|
|
@@ -3396,7 +3607,7 @@ async function runCli(rawArgs = process.argv.slice(2)) {
|
|
|
3396
3607
|
if (shouldLaunchSkillsUi(rawArgs, terminalEnvironment)) {
|
|
3397
3608
|
const { resolveUiLocale } = await import("./locale-HT44UIUP.mjs");
|
|
3398
3609
|
const locale = resolveUiLocale("auto", process.env.RSP_UI_LANG, process.env.LC_ALL ?? process.env.LC_MESSAGES ?? process.env.LANG ?? new Intl.DateTimeFormat().resolvedOptions().locale);
|
|
3399
|
-
const { runSkillsTui } = await import("./entry-
|
|
3610
|
+
const { runSkillsTui } = await import("./entry-KAWA2AS4.mjs");
|
|
3400
3611
|
const selection = await runSkillsTui(locale);
|
|
3401
3612
|
if (selection.kind === "confirmed") {
|
|
3402
3613
|
const result = await installPackagedSkills({ names: selection.names, force: selection.force });
|
|
@@ -3414,7 +3625,7 @@ async function runCli(rawArgs = process.argv.slice(2)) {
|
|
|
3414
3625
|
} else {
|
|
3415
3626
|
const { resolveUiLocale } = await import("./locale-HT44UIUP.mjs");
|
|
3416
3627
|
const locale = resolveUiLocale(lang, process.env.RSP_UI_LANG, process.env.LC_ALL ?? process.env.LC_MESSAGES ?? process.env.LANG ?? new Intl.DateTimeFormat().resolvedOptions().locale);
|
|
3417
|
-
const { runTui } = await import("./entry-
|
|
3628
|
+
const { runTui } = await import("./entry-IPHJBI53.mjs");
|
|
3418
3629
|
const exitCode = await runTui(locale);
|
|
3419
3630
|
if (exitCode !== 0)
|
|
3420
3631
|
process.exitCode = exitCode;
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
projectDependencyForest,
|
|
8
8
|
queryArchiveHistory,
|
|
9
9
|
readArchiveHistoryDetail
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MUNLLVVH.mjs";
|
|
11
11
|
import {
|
|
12
12
|
displayWidth,
|
|
13
13
|
openTerminalSession,
|
|
14
14
|
truncateDisplay,
|
|
15
15
|
wrapDisplay
|
|
16
16
|
} from "./chunk-JOMFFQRG.mjs";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-WJXKLLIF.mjs";
|
|
18
18
|
|
|
19
19
|
// src/tui/entry.tsx
|
|
20
20
|
import process from "process";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
inspectPackagedSkillInventory
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3Z4IO2TD.mjs";
|
|
5
5
|
import {
|
|
6
6
|
openTerminalSession,
|
|
7
7
|
truncateDisplay
|
|
8
8
|
} from "./chunk-JOMFFQRG.mjs";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-WJXKLLIF.mjs";
|
|
10
10
|
|
|
11
11
|
// src/skills-tui/entry.tsx
|
|
12
12
|
import process from "process";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oevery/rsp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.1",
|
|
5
5
|
"description": "RSP (Reliable Software Practice), a repository-native engineering workflow for humans and AI agents",
|
|
6
6
|
"author": "oevery",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,13 +37,18 @@
|
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"prepare": "pnpm run build",
|
|
40
|
-
"release:check": "pnpm run release:metadata-check && pnpm run build && pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run release:package-check",
|
|
40
|
+
"release:check": "pnpm run release:metadata-check && pnpm run docs:check && pnpm run docs:build && pnpm run build && pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run release:package-check",
|
|
41
41
|
"release:candidate-check": "node scripts/release-candidate-check.mjs && pnpm run release:check",
|
|
42
42
|
"release:metadata-check": "node scripts/release-metadata-check.mjs",
|
|
43
43
|
"release:package-check": "node scripts/clean-install-check.mjs",
|
|
44
44
|
"prepublishOnly": "pnpm run release:candidate-check",
|
|
45
45
|
"build": "tsup",
|
|
46
46
|
"dev": "tsup --watch",
|
|
47
|
+
"docs:build": "vitepress build docs",
|
|
48
|
+
"docs:check": "node scripts/docs-check.mjs",
|
|
49
|
+
"docs:deploy": "wrangler deploy",
|
|
50
|
+
"docs:dev": "vitepress dev docs",
|
|
51
|
+
"docs:preview": "vitepress preview docs",
|
|
47
52
|
"lint": "eslint .",
|
|
48
53
|
"lint:fix": "eslint --fix .",
|
|
49
54
|
"typecheck": "tsc --noEmit",
|
|
@@ -65,6 +70,8 @@
|
|
|
65
70
|
"ink-testing-library": "4.0.0",
|
|
66
71
|
"tsup": "^8.5.1",
|
|
67
72
|
"typescript": "^5.9.3",
|
|
68
|
-
"
|
|
73
|
+
"vitepress": "^1.6.4",
|
|
74
|
+
"vitest": "^4.1.10",
|
|
75
|
+
"wrangler": "^4.114.0"
|
|
69
76
|
}
|
|
70
77
|
}
|
package/rules/rsp-rules.md
CHANGED
|
@@ -34,7 +34,7 @@ This file is the minimal fallback protocol for agents that cannot load the `rsp`
|
|
|
34
34
|
- Parallel Changes in one Group follow the Brief `Slices` declaration order in plan output; unrelated work uses stable lexical ordering.
|
|
35
35
|
- A Group Brief blocker is inherited by its direct child Changes as an external blocker; it does not create inferred dependency edges.
|
|
36
36
|
- Persist only `open` and `archived`; readiness, blockers, verification, and next actions are derived.
|
|
37
|
-
- When fresh evidence shows that
|
|
37
|
+
- When fresh evidence shows that archived acceptance was incomplete, restore the same owner explicitly. `rsp reopen <work-ref> --reason <text>` retains the selected archive, focuses the executable Change, and adds unfinished Task and Verify evidence. If the enclosing Group is closed, first run `rsp group reopen <group> --reason <text>` to restore only its retained Brief as unfocused work with unfinished completion evidence, then separately reopen only the incomplete child. Multiple matching Change or Group archives require exact `--from .rsp/archives/...` selection. Neither command cascades into children or dependents, and `rsp group create` never reuses archived identity. Open work wins over same-identity history in current state; genuinely new or independently delivered scope uses a new owner.
|
|
38
38
|
- Ordinary implementation is the default after unexplained failures are ruled out. Use test-first only when authority requires it or a concrete risk makes pre-mutation failure materially safer. Retain a new test only when it protects observable behavior or a real boundary with distinct proportionate value; otherwise remove the temporary probe and use decisive final verification.
|
|
39
39
|
- Before later-turn mutation under a direct report, design, tiny, or small route, rederive from the newly authorized objective and prospective work. Cross-module implementation, multiple acceptance surfaces, repeated production-path correction, real-host validation, bounded review convergence, lifecycle delivery, or a clear ready successor requires the smallest sufficient WorkRef and fresh qualification before mutation; unchanged tiny/small follow-ups stay direct. Elapsed time and message count alone never escalate work.
|
|
40
40
|
- Before focusing or mutating a different WorkRef, compare dirty product or durable-truth paths with the prior owner's declared or observed paths. Overlap requires continuation of the same open WorkRef, an authorized reopen, an explicitly authorized integration owner, or a stop for boundary resolution. Disjoint authorized work may proceed without staging or forcing a commit; insufficient ownership evidence stops the transition.
|
|
@@ -59,6 +59,8 @@ This file is the minimal fallback protocol for agents that cannot load the `rsp`
|
|
|
59
59
|
|
|
60
60
|
## Continuation and conflicts
|
|
61
61
|
|
|
62
|
+
- During managed execution, treat a progress or status inquiry as an evidence update, not a stop signal, and continue authorized work while authority and blockers permit it. An explicit pause stops and confirms active workers before acknowledgement, preserves the focused owner, and permits no further mutation until resume. Only an explicit release or unfocus request clears owner selection; environment and verification blockers preserve the focused owner.
|
|
63
|
+
- Resume treats continuation prose only as pointers: reread authority, status, focus, owned diff, blockers, and decisive evidence, invalidate stale claims, and requalify Manage before mutation or dispatch. Never persist paused, worker, controller, or execution-chronology state.
|
|
62
64
|
- When execution stops with accepted work remaining, return one compact continuation with the semantic fields `WorkRef`, `Authority`, `Current state`, `Changed artifacts`, `Fresh verification`, `Blockers`, and `Next action`. Keep this field order, but localize the human-facing title and labels to the response language without changing referenced artifact prose or canonical technical values. Reopen its authority pointers, inspect drift, and refresh verification before resuming; it is not durable truth or a second state store.
|
|
63
65
|
- Write a continuation file only when explicitly authorized. Never persist hidden handoff or controller state.
|
|
64
66
|
- If an active merge, rebase, or cherry-pick conflict intersects authorized implementation, inspect the exact operation and conflicted paths, then compare base/ours/theirs semantics in that operation. Resolve only evidenced in-scope content while preserving unrelated work.
|
|
@@ -69,6 +71,6 @@ This file is the minimal fallback protocol for agents that cannot load the `rsp`
|
|
|
69
71
|
|
|
70
72
|
- Do not create RSP-managed files directly when a command owns the operation.
|
|
71
73
|
- Do not create archive entries directly; use `npx -y @oevery/rsp archive <name>`.
|
|
72
|
-
- Do not restore archived Change content directly; use `npx -y @oevery/rsp reopen <name> --reason <text>` and exact `--from` selection when required.
|
|
74
|
+
- Do not restore archived Change or Group Brief content directly; use `npx -y @oevery/rsp reopen <name> --reason <text>` or `npx -y @oevery/rsp group reopen <group> --reason <text>` and exact `--from` selection when required.
|
|
73
75
|
- Do not modify content outside the managed RSP block in `AGENTS.md` unless explicitly requested.
|
|
74
76
|
- Archive reports warnings but does not grant commit, push, publication, deletion, or external approval authority.
|