filegrc 0.3.1 → 0.3.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 +2 -2
- package/package.json +1 -1
- package/src/agent.js +1 -1
- package/src/cli.js +177 -49
- package/src/program-path.js +8 -3
- package/src/program-readiness.js +39 -8
- package/src/setup.js +13 -6
- package/src/web.js +4 -4
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npx filegrc serve
|
|
|
20
20
|
npx filegrc setup --help
|
|
21
21
|
npx filegrc build
|
|
22
22
|
npx filegrc guide risk-assessment
|
|
23
|
-
npx filegrc program-path --json
|
|
23
|
+
npx filegrc program-path --next --json
|
|
24
24
|
npx filegrc scaffold risk-assessment --title "2026 Annual Risk Assessment"
|
|
25
25
|
npx filegrc list risk --json
|
|
26
26
|
npx filegrc references risk-example --json
|
|
@@ -38,7 +38,7 @@ npx filegrc evidence-packet --start 2026-01-01 --end 2026-06-30 --audit audit-id
|
|
|
38
38
|
|
|
39
39
|
`filegrc setup` provides the headless equivalent of browser onboarding. Run it without arguments for guided terminal setup, or pass all initial service-boundary fields and a management program goal as flags or a JSON payload. Add `--preview` to validate and inspect the planned service and workspace writes without saving. Add `--summary --json` for compact agent output. Selecting Type 1 or Type 2 updates the workspace goal and selected systems. Setup does not select framework records, link controls, create evidence, or create an audit record.
|
|
40
40
|
|
|
41
|
-
`filegrc program-path` gives
|
|
41
|
+
`filegrc program-path --next --json` gives an agent the current step and first action without loading the full lifecycle. Use `--summary` for compact status across all six steps, `--current` for the full current-step guide, or no compact flag for every step. `filegrc guide <type>` repeats the matching page guidance and adds fields, relationship candidates, Markdown slots, and timing for that record type.
|
|
42
42
|
|
|
43
43
|
`filegrc program-readiness` reports whether management can start a candidate Type 2 period. Add `--summary --json` for compact stage counts and next actions, or omit `--summary` for every readiness item. Use `--require-ready` in automation. The command does not require an audit ID or CPA firm.
|
|
44
44
|
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -93,7 +93,7 @@ export function buildAgentGuide(loaded, type, options = {}) {
|
|
|
93
93
|
"Inspect existing records and relation candidates before writing.",
|
|
94
94
|
"Create a scaffold, then replace every null value and empty required array with facts from an authoritative source.",
|
|
95
95
|
"Keep stable metadata in JSON and put the work performed, inputs, results, decisions, exceptions, and follow-up in the recommended Markdown companion.",
|
|
96
|
-
"Run filegrc validate, review the full Git diff, and commit the JSON, Markdown, and attachments together with a message that explains why the record changed."
|
|
96
|
+
"Run npx filegrc validate, review the full Git diff, and commit the JSON, Markdown, and attachments together with a message that explains why the record changed."
|
|
97
97
|
],
|
|
98
98
|
completionChecks: [
|
|
99
99
|
"The record describes work that actually occurred; planned work is not marked complete.",
|
package/src/cli.js
CHANGED
|
@@ -37,10 +37,12 @@ import { loadWorkspace } from "./workspace.js";
|
|
|
37
37
|
const BOOLEAN_FLAGS = new Set([
|
|
38
38
|
"check-docs",
|
|
39
39
|
"complete",
|
|
40
|
+
"current",
|
|
40
41
|
"draft",
|
|
41
42
|
"help",
|
|
42
43
|
"json",
|
|
43
44
|
"mutation",
|
|
45
|
+
"next",
|
|
44
46
|
"preview",
|
|
45
47
|
"require-ready",
|
|
46
48
|
"summary",
|
|
@@ -95,8 +97,11 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
95
97
|
else {
|
|
96
98
|
console.log(`${result.draft ? "Saved draft scope" : "Completed initial setup"} for ${result.system.title}.`);
|
|
97
99
|
console.log(`System: ${result.system.id} (${result.system.status})`);
|
|
100
|
+
if (result.draft) {
|
|
101
|
+
console.log("Planned and in scope means selected for scope review, not approved or active.");
|
|
102
|
+
}
|
|
98
103
|
console.log(`Target: ${result.workspace.assuranceGoal}`);
|
|
99
|
-
console.log("Next: finish Step 1 by confirming people, criteria, commitments, vendors, and in-scope systems. Run filegrc program-path
|
|
104
|
+
console.log("Next: finish Step 1 by confirming people, criteria, commitments, vendors, and in-scope systems. Run npx filegrc program-path --next --json.");
|
|
100
105
|
}
|
|
101
106
|
return output;
|
|
102
107
|
}
|
|
@@ -165,9 +170,10 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
165
170
|
const auditId = positionals[0] || flags.audit;
|
|
166
171
|
const auditReadiness = auditId ? await assessAuditPreparation(loaded, { auditId }) : null;
|
|
167
172
|
const result = buildProgramPathResult(loaded.model, readiness, auditReadiness);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
173
|
+
const output = selectProgramPathOutput(result, flags);
|
|
174
|
+
if (flags.json) console.log(JSON.stringify(output, null, 2));
|
|
175
|
+
else printProgramPathOutput(output, flags);
|
|
176
|
+
return output;
|
|
171
177
|
}
|
|
172
178
|
if (command === "scaffold") {
|
|
173
179
|
const loaded = await loadWorkspace(root);
|
|
@@ -678,7 +684,7 @@ Usage:
|
|
|
678
684
|
filegrc describe <resource-type>
|
|
679
685
|
filegrc types [--json]
|
|
680
686
|
filegrc guide [resource-type] [--id resource-id] [--json]
|
|
681
|
-
filegrc program-path [audit-id] [--as-of YYYY-MM-DD] [--json]
|
|
687
|
+
filegrc program-path [audit-id] [--as-of YYYY-MM-DD] [--summary|--next|--current] [--json]
|
|
682
688
|
filegrc scaffold <resource-type> --title text [--id resource-id]
|
|
683
689
|
filegrc list [resource-type] [--json]
|
|
684
690
|
filegrc search <query> [--type resource-type] [--json]
|
|
@@ -773,7 +779,10 @@ and current readiness state. Pass an audit ID to include Step 6 status.
|
|
|
773
779
|
Options:
|
|
774
780
|
--audit <id> Audit record to use for Step 6
|
|
775
781
|
--as-of <date> Evaluate readiness on YYYY-MM-DD
|
|
776
|
-
--
|
|
782
|
+
--summary Print compact status and the first action for all six steps
|
|
783
|
+
--next Print only the current step and its first action
|
|
784
|
+
--current Print the full guide for the current step only
|
|
785
|
+
--json Print the selected path view as JSON
|
|
777
786
|
--root <path> Workspace path
|
|
778
787
|
--help Show this help`);
|
|
779
788
|
return;
|
|
@@ -797,44 +806,48 @@ Options:
|
|
|
797
806
|
}
|
|
798
807
|
|
|
799
808
|
function agentOverview(model) {
|
|
809
|
+
const commands = {
|
|
810
|
+
help: "filegrc help",
|
|
811
|
+
version: "filegrc version",
|
|
812
|
+
serve: "filegrc serve [root]",
|
|
813
|
+
setup: "filegrc setup [setup.json|-] [--draft] [--preview] [--summary] [--json]",
|
|
814
|
+
build: "filegrc build [root]",
|
|
815
|
+
validate: "filegrc validate [root] --json",
|
|
816
|
+
model: "filegrc model --json",
|
|
817
|
+
describe: "filegrc describe <resource-type>",
|
|
818
|
+
types: "filegrc types --json",
|
|
819
|
+
guide: "filegrc guide [resource-type] --json",
|
|
820
|
+
programPath: "filegrc program-path [audit-id] --next --json",
|
|
821
|
+
scaffold: "filegrc scaffold <resource-type> --title <name>",
|
|
822
|
+
list: "filegrc list [resource-type] --json",
|
|
823
|
+
search: "filegrc search <query> --json",
|
|
824
|
+
obligations: "filegrc obligations --json",
|
|
825
|
+
programReadiness: "filegrc program-readiness --json",
|
|
826
|
+
evidenceTestDrafts: "filegrc evidence-test-drafts --preview --json",
|
|
827
|
+
auditReadiness: "filegrc audit-readiness <audit-id> --json",
|
|
828
|
+
prepareAudit: "filegrc prepare-audit <audit-id>",
|
|
829
|
+
trigger: "filegrc trigger <event-type> <date-or-time-and-subject-flags>",
|
|
830
|
+
evidencePacket: "filegrc evidence-packet --audit <audit-id> --preview --json",
|
|
831
|
+
get: "filegrc get <resource-id> [--mutation]",
|
|
832
|
+
references: "filegrc references <resource-id> --json",
|
|
833
|
+
create: "filegrc create <record-or-mutation.json>",
|
|
834
|
+
complete: "filegrc complete <obligation-id> <completion-mutation.json>",
|
|
835
|
+
completeAction: "filegrc complete-action <action-item-id> <completion-mutation.json> --completed-on <date>",
|
|
836
|
+
completeEvent: "filegrc complete-event <obligation-event-id> --completed-on <date>",
|
|
837
|
+
update: "filegrc update <resource-type> <id> <record-or-mutation.json>",
|
|
838
|
+
content: "filegrc content <resource-type> <id> [slot] [--write <markdown-file|->]",
|
|
839
|
+
attach: "filegrc attach <evidence-id> <source-file> [--name <file-name>]",
|
|
840
|
+
detach: "filegrc detach <evidence-id> <attachment-name> --yes",
|
|
841
|
+
delete: "filegrc delete <resource-type> <id> --yes",
|
|
842
|
+
commit: "git diff --check && git diff && git add <reviewed-paths> && git commit -m <reason>"
|
|
843
|
+
};
|
|
800
844
|
return {
|
|
801
845
|
rule: "Treat data/ as the source of truth. Run guide before creating an unfamiliar type, validate after every write, review the Git diff, then commit a focused change.",
|
|
802
846
|
programPath: buildAgentProgramPath(model),
|
|
803
|
-
actions:
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
setup: "filegrc setup [setup.json|-] [--draft] [--preview] [--summary] [--json]",
|
|
808
|
-
build: "filegrc build [root]",
|
|
809
|
-
validate: "filegrc validate [root] --json",
|
|
810
|
-
model: "filegrc model --json",
|
|
811
|
-
describe: "filegrc describe <resource-type>",
|
|
812
|
-
types: "filegrc types --json",
|
|
813
|
-
guide: "filegrc guide [resource-type] --json",
|
|
814
|
-
programPath: "filegrc program-path [audit-id] --json",
|
|
815
|
-
scaffold: "filegrc scaffold <resource-type> --title <name>",
|
|
816
|
-
list: "filegrc list [resource-type] --json",
|
|
817
|
-
search: "filegrc search <query> --json",
|
|
818
|
-
obligations: "filegrc obligations --json",
|
|
819
|
-
programReadiness: "filegrc program-readiness --json",
|
|
820
|
-
evidenceTestDrafts: "filegrc evidence-test-drafts --preview --json",
|
|
821
|
-
auditReadiness: "filegrc audit-readiness <audit-id> --json",
|
|
822
|
-
prepareAudit: "filegrc prepare-audit <audit-id>",
|
|
823
|
-
trigger: "filegrc trigger <event-type> <date-or-time-and-subject-flags>",
|
|
824
|
-
evidencePacket: "filegrc evidence-packet --audit <audit-id> --preview --json",
|
|
825
|
-
get: "filegrc get <resource-id> [--mutation]",
|
|
826
|
-
references: "filegrc references <resource-id> --json",
|
|
827
|
-
create: "filegrc create <record-or-mutation.json>",
|
|
828
|
-
complete: "filegrc complete <obligation-id> <completion-mutation.json>",
|
|
829
|
-
completeAction: "filegrc complete-action <action-item-id> <completion-mutation.json> --completed-on <date>",
|
|
830
|
-
completeEvent: "filegrc complete-event <obligation-event-id> --completed-on <date>",
|
|
831
|
-
update: "filegrc update <resource-type> <id> <record-or-mutation.json>",
|
|
832
|
-
content: "filegrc content <resource-type> <id> [slot] [--write <markdown-file|->]",
|
|
833
|
-
attach: "filegrc attach <evidence-id> <source-file> [--name <file-name>]",
|
|
834
|
-
detach: "filegrc detach <evidence-id> <attachment-name> --yes",
|
|
835
|
-
delete: "filegrc delete <resource-type> <id> --yes",
|
|
836
|
-
commit: "git diff --check && git diff && git add <reviewed-paths> && git commit -m <reason>"
|
|
837
|
-
},
|
|
847
|
+
actions: Object.fromEntries(Object.entries(commands).map(([name, command]) => [
|
|
848
|
+
name,
|
|
849
|
+
command.startsWith("filegrc ") ? `npx ${command}` : command
|
|
850
|
+
])),
|
|
838
851
|
resourceTypes: listResourceTypes(model).map(({ type, title, group }) => ({ type, title, group }))
|
|
839
852
|
};
|
|
840
853
|
}
|
|
@@ -885,12 +898,12 @@ function printAgentGuide(result) {
|
|
|
885
898
|
for (const field of relationshipFields) {
|
|
886
899
|
const hasCandidates = field.relation.candidates.length > 0;
|
|
887
900
|
const suffix = field.relation.truncated && hasCandidates
|
|
888
|
-
? `, … (${field.relation.candidateCount} total; use filegrc list)`
|
|
901
|
+
? `, … (${field.relation.candidateCount} total; use npx filegrc list)`
|
|
889
902
|
: "";
|
|
890
903
|
const candidates = hasCandidates
|
|
891
904
|
? field.relation.candidates.join(", ") + suffix
|
|
892
905
|
: field.relation.candidateCount
|
|
893
|
-
? `use filegrc list (${field.relation.candidateCount} possible)`
|
|
906
|
+
? `use npx filegrc list (${field.relation.candidateCount} possible)`
|
|
894
907
|
: "none";
|
|
895
908
|
console.log(`${field.name}\t${field.relation.types.join("|")}\t${candidates}`);
|
|
896
909
|
}
|
|
@@ -957,19 +970,130 @@ function printProgramPath(result) {
|
|
|
957
970
|
}
|
|
958
971
|
}
|
|
959
972
|
|
|
973
|
+
function selectProgramPathOutput(result, flags) {
|
|
974
|
+
const modes = ["summary", "next", "current"].filter((name) => flags[name]);
|
|
975
|
+
if (modes.length > 1) throw new Error("Use only one of --summary, --next, or --current.");
|
|
976
|
+
if (flags.summary) return summarizeProgramPath(result);
|
|
977
|
+
if (flags.next) return nextProgramPath(result);
|
|
978
|
+
if (flags.current) {
|
|
979
|
+
const stage = result.stages.find(({ id }) => id === result.currentStep.id);
|
|
980
|
+
return { ...result, stages: stage ? [stage] : [] };
|
|
981
|
+
}
|
|
982
|
+
return result;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function summarizeProgramPath(result) {
|
|
986
|
+
return {
|
|
987
|
+
schemaVersion: result.schemaVersion,
|
|
988
|
+
asOf: result.asOf,
|
|
989
|
+
currentStep: result.currentStep,
|
|
990
|
+
evidenceReady: result.evidenceReady,
|
|
991
|
+
operating: result.operating,
|
|
992
|
+
stages: result.stages.map((stage) => ({
|
|
993
|
+
id: stage.id,
|
|
994
|
+
number: stage.number,
|
|
995
|
+
title: stage.title,
|
|
996
|
+
status: stage.status,
|
|
997
|
+
counts: stage.counts,
|
|
998
|
+
nextAction: summarizePathAction(stage.nextActions[0])
|
|
999
|
+
}))
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function nextProgramPath(result) {
|
|
1004
|
+
const stage = result.stages.find(({ id }) => id === result.currentStep.id);
|
|
1005
|
+
const nextAction = stage?.nextActions[0];
|
|
1006
|
+
return {
|
|
1007
|
+
schemaVersion: result.schemaVersion,
|
|
1008
|
+
asOf: result.asOf,
|
|
1009
|
+
currentStep: result.currentStep,
|
|
1010
|
+
evidenceReady: result.evidenceReady,
|
|
1011
|
+
operating: result.operating,
|
|
1012
|
+
step: stage ? {
|
|
1013
|
+
id: stage.id,
|
|
1014
|
+
number: stage.number,
|
|
1015
|
+
title: stage.title,
|
|
1016
|
+
status: stage.status,
|
|
1017
|
+
summary: stage.summary,
|
|
1018
|
+
nextAction: summarizePathAction(nextAction),
|
|
1019
|
+
commands: nextActionCommands(stage, nextAction)
|
|
1020
|
+
} : null
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function summarizePathAction(action) {
|
|
1025
|
+
if (!action) return null;
|
|
1026
|
+
return {
|
|
1027
|
+
id: action.id,
|
|
1028
|
+
status: action.status,
|
|
1029
|
+
title: action.title,
|
|
1030
|
+
message: action.message,
|
|
1031
|
+
...(action.resourceType ? { resourceType: action.resourceType } : {}),
|
|
1032
|
+
...(action.resourceId ? { resourceId: action.resourceId } : {})
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function nextActionCommands(stage, action) {
|
|
1037
|
+
if (action?.commands?.length) return action.commands;
|
|
1038
|
+
if (!action?.resourceType) return stage.commands;
|
|
1039
|
+
const resourceType = shellArgument(action.resourceType);
|
|
1040
|
+
const commands = [`npx filegrc guide ${resourceType} --json`];
|
|
1041
|
+
if (action.resourceId) {
|
|
1042
|
+
const resourceId = shellArgument(action.resourceId);
|
|
1043
|
+
commands.push(`npx filegrc get ${resourceId} --mutation`);
|
|
1044
|
+
commands.push(`npx filegrc update ${resourceType} ${resourceId} MUTATION.json --json`);
|
|
1045
|
+
} else {
|
|
1046
|
+
commands.push(`npx filegrc list ${resourceType} --json`);
|
|
1047
|
+
commands.push(`npx filegrc scaffold ${resourceType} --title "NAME"`);
|
|
1048
|
+
commands.push("npx filegrc create MUTATION.json --json");
|
|
1049
|
+
}
|
|
1050
|
+
return commands;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function shellArgument(value) {
|
|
1054
|
+
const text = String(value);
|
|
1055
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(text)
|
|
1056
|
+
? text
|
|
1057
|
+
: `'${text.replaceAll("'", "'\\''")}'`;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function printProgramPathOutput(result, flags) {
|
|
1061
|
+
if (flags.summary) {
|
|
1062
|
+
console.log(`Current: Step ${result.currentStep.number}, ${result.currentStep.title}`);
|
|
1063
|
+
for (const stage of result.stages) {
|
|
1064
|
+
console.log(`${String(stage.status).toUpperCase()}\tStep ${stage.number}\t${stage.title}`);
|
|
1065
|
+
}
|
|
1066
|
+
const current = result.stages.find(({ id }) => id === result.currentStep.id);
|
|
1067
|
+
if (current?.nextAction) console.log(`Next: ${current.nextAction.title} · ${current.nextAction.message}`);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
if (flags.next) {
|
|
1071
|
+
console.log(`Current: Step ${result.currentStep.number}, ${result.currentStep.title}`);
|
|
1072
|
+
if (result.step?.nextAction) {
|
|
1073
|
+
console.log(`Next: ${result.step.nextAction.title} · ${result.step.nextAction.message}`);
|
|
1074
|
+
}
|
|
1075
|
+
for (const command of result.step?.commands || []) console.log(` ${command}`);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
printProgramPath(result);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
960
1081
|
function summarizeProgramReadiness(result) {
|
|
961
1082
|
const ownership = result.stages
|
|
962
1083
|
.flatMap((stage) => stage.items)
|
|
963
1084
|
.find((item) => item.id === "program-ownership");
|
|
964
|
-
const summarizeItem = (item) => item ? {
|
|
1085
|
+
const summarizeItem = (item, options = {}) => item ? {
|
|
965
1086
|
id: item.id,
|
|
966
1087
|
status: item.status,
|
|
967
1088
|
title: item.title,
|
|
968
|
-
message: item.message,
|
|
1089
|
+
...(options.message === false ? {} : { message: item.message }),
|
|
969
1090
|
...(item.resourceType ? { resourceType: item.resourceType } : {}),
|
|
970
|
-
...(item.resourceId ? { resourceId: item.resourceId } : {})
|
|
971
|
-
...(item.unresolvedAssignments?.length ? { unresolvedAssignments: item.unresolvedAssignments } : {})
|
|
1091
|
+
...(item.resourceId ? { resourceId: item.resourceId } : {})
|
|
972
1092
|
} : null;
|
|
1093
|
+
const unresolvedOwnership = ownership?.unresolvedAssignments || [];
|
|
1094
|
+
const ownershipReasons = unresolvedOwnership
|
|
1095
|
+
.flatMap((assignment) => assignment.reasons || [])
|
|
1096
|
+
.reduce((counts, { reason }) => ({ ...counts, [reason]: (counts[reason] || 0) + 1 }), {});
|
|
973
1097
|
return {
|
|
974
1098
|
schemaVersion: result.schemaVersion,
|
|
975
1099
|
generatedAt: result.generatedAt,
|
|
@@ -985,14 +1109,18 @@ function summarizeProgramReadiness(result) {
|
|
|
985
1109
|
scopeCounts: Object.fromEntries(
|
|
986
1110
|
Object.entries(result.scope).map(([name, ids]) => [name.replace(/Ids$/, ""), ids.length])
|
|
987
1111
|
),
|
|
988
|
-
unresolvedOwnership:
|
|
1112
|
+
unresolvedOwnership: {
|
|
1113
|
+
count: unresolvedOwnership.length,
|
|
1114
|
+
byReason: ownershipReasons,
|
|
1115
|
+
resourceIds: unresolvedOwnership.map(({ resourceId }) => resourceId)
|
|
1116
|
+
},
|
|
989
1117
|
firstAction: summarizeItem(result.firstAction),
|
|
990
1118
|
stages: result.stages.map((stage) => ({
|
|
991
1119
|
id: stage.id,
|
|
992
1120
|
title: stage.title,
|
|
993
1121
|
status: stage.status,
|
|
994
1122
|
counts: stage.counts,
|
|
995
|
-
firstAction: summarizeItem(stage.items.find(({ status }) => status === "action"))
|
|
1123
|
+
firstAction: summarizeItem(stage.items.find(({ status }) => status === "action"), { message: false })
|
|
996
1124
|
}))
|
|
997
1125
|
};
|
|
998
1126
|
}
|
package/src/program-path.js
CHANGED
|
@@ -235,8 +235,8 @@ export function buildAgentProgramPath(model) {
|
|
|
235
235
|
instructions: RESOURCE_INSTRUCTIONS[type] || definition.description,
|
|
236
236
|
use: definition.description,
|
|
237
237
|
policyBasis: definition.guidance.policyBasis,
|
|
238
|
-
guide: `filegrc guide ${type} --json`,
|
|
239
|
-
list: `filegrc list ${type} --json`
|
|
238
|
+
guide: `npx filegrc guide ${type} --json`,
|
|
239
|
+
list: `npx filegrc list ${type} --json`
|
|
240
240
|
};
|
|
241
241
|
});
|
|
242
242
|
const utilityPages = (stage.utilities || []).map((utility, index) => ({
|
|
@@ -246,16 +246,21 @@ export function buildAgentProgramPath(model) {
|
|
|
246
246
|
instructions: utility.instructions,
|
|
247
247
|
use: utility.use,
|
|
248
248
|
policyBasis: utility.policyBasis,
|
|
249
|
-
commands: utility.commands
|
|
249
|
+
commands: utility.commands.map(agentCommand)
|
|
250
250
|
}));
|
|
251
251
|
return {
|
|
252
252
|
...stage,
|
|
253
|
+
commands: stage.commands.map(agentCommand),
|
|
253
254
|
pages: stage.id === "run" ? utilityPages : [...resourcePages, ...utilityPages],
|
|
254
255
|
...(stage.id === "run" ? { operatingRecords: resourcePages } : {})
|
|
255
256
|
};
|
|
256
257
|
});
|
|
257
258
|
}
|
|
258
259
|
|
|
260
|
+
function agentCommand(command) {
|
|
261
|
+
return command.startsWith("filegrc ") ? `npx ${command}` : command;
|
|
262
|
+
}
|
|
263
|
+
|
|
259
264
|
export function resourceProgramContext(type) {
|
|
260
265
|
const stage = PROGRAM_PATH.find((candidate) => (
|
|
261
266
|
candidate.resourceTypes.includes(type) || (candidate.supportingResourceTypes || []).includes(type)
|
package/src/program-readiness.js
CHANGED
|
@@ -213,12 +213,10 @@ function programOwnershipItem(records, byId) {
|
|
|
213
213
|
const detail = [];
|
|
214
214
|
if (!currentOwners.size) detail.push("No current person owns the program records.");
|
|
215
215
|
if (unresolved.length) {
|
|
216
|
-
detail.push(
|
|
217
|
-
`${unresolved.length} ${unresolved.length === 1 ? "record has" : "records have"} no current person owner: ` +
|
|
218
|
-
`${unresolvedAssignments.map(({ title, resourceId }) => `${title} (${resourceId})`).join(", ")}.`
|
|
219
|
-
);
|
|
216
|
+
detail.push(`${unresolved.length} ${unresolved.length === 1 ? "record has" : "records have"} no current person owner.`);
|
|
220
217
|
}
|
|
221
218
|
if (!oversightComplete) detail.push("Finish and activate Security and Risk Oversight with a current chair who is separate from policy ownership.");
|
|
219
|
+
const oversightId = oversight?.id ? shellArgument(oversight.id) : null;
|
|
222
220
|
return item(
|
|
223
221
|
"program-ownership",
|
|
224
222
|
complete ? "complete" : "action",
|
|
@@ -227,7 +225,18 @@ function programOwnershipItem(records, byId) {
|
|
|
227
225
|
? `${currentOwners.size} current ${currentOwners.size === 1 ? "person owns" : "people own"} the program records.${oversight ? " Security and Risk Oversight has a separate current chair." : ""}`
|
|
228
226
|
: detail.join(" "),
|
|
229
227
|
!oversightComplete ? oversight : unresolved[0] || { type: "person" },
|
|
230
|
-
{
|
|
228
|
+
{
|
|
229
|
+
unresolvedAssignments,
|
|
230
|
+
...(!oversightComplete && oversight ? {
|
|
231
|
+
commands: [
|
|
232
|
+
"npx filegrc guide person --json",
|
|
233
|
+
"npx filegrc list person --json",
|
|
234
|
+
'npx filegrc scaffold person --title "REVIEWER NAME" | npx filegrc create - --json',
|
|
235
|
+
`npx filegrc get ${oversightId} --mutation`,
|
|
236
|
+
`npx filegrc update team ${oversightId} MUTATION.json --json`
|
|
237
|
+
]
|
|
238
|
+
} : {})
|
|
239
|
+
}
|
|
231
240
|
);
|
|
232
241
|
}
|
|
233
242
|
|
|
@@ -259,15 +268,30 @@ async function policiesStage(scope, byId, readMarkdown, asOf) {
|
|
|
259
268
|
.flatMap((policy) => [...currentPartyPeople(policy.approverIds || [], byId)])
|
|
260
269
|
.map((id) => byId.get(id))
|
|
261
270
|
.find(Boolean);
|
|
271
|
+
const policyOwnerIds = new Set(policies.flatMap((policy) => (
|
|
272
|
+
[...partyPeople(policy.ownerIds || [], byId)]
|
|
273
|
+
)));
|
|
274
|
+
const oversight = byId.get("team-security-risk-oversight");
|
|
275
|
+
const availableReviewer = oversight?.type === "team" && oversight.status === "active"
|
|
276
|
+
? [...currentPartyPeople(oversight.chairIds || [], byId)]
|
|
277
|
+
.filter((id) => !policyOwnerIds.has(id))
|
|
278
|
+
.map((id) => byId.get(id))
|
|
279
|
+
.find(Boolean)
|
|
280
|
+
: null;
|
|
281
|
+
const reviewerNeedsAssignment = !appointedReviewer && availableReviewer && policies.length;
|
|
262
282
|
const items = [
|
|
263
283
|
item(
|
|
264
284
|
"independent-reviewer",
|
|
265
285
|
appointedReviewer ? "complete" : "action",
|
|
266
|
-
|
|
286
|
+
reviewerNeedsAssignment
|
|
287
|
+
? "Assign the independent policy reviewer"
|
|
288
|
+
: "Appoint the independent policy reviewer",
|
|
267
289
|
appointedReviewer
|
|
268
290
|
? `${appointedReviewer.title} is recorded as a reviewer separate from policy ownership.`
|
|
269
|
-
:
|
|
270
|
-
|
|
291
|
+
: reviewerNeedsAssignment
|
|
292
|
+
? `${availableReviewer.title} chairs Security and Risk Oversight. Assign this person as approver on each policy after review.`
|
|
293
|
+
: "Appoint a reviewer who is separate from the policy owner. The reviewer may be another person in the organization or an external person, and is separate from the CPA firm that may later perform the audit.",
|
|
294
|
+
appointedReviewer || (reviewerNeedsAssignment ? policies[0] : { type: "person" })
|
|
271
295
|
)
|
|
272
296
|
];
|
|
273
297
|
if (!policies.length) {
|
|
@@ -670,6 +694,13 @@ function item(id, status, title, message, resource = {}, details = {}) {
|
|
|
670
694
|
};
|
|
671
695
|
}
|
|
672
696
|
|
|
697
|
+
function shellArgument(value) {
|
|
698
|
+
const text = String(value);
|
|
699
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(text)
|
|
700
|
+
? text
|
|
701
|
+
: `'${text.replaceAll("'", "'\\''")}'`;
|
|
702
|
+
}
|
|
703
|
+
|
|
673
704
|
function countStatuses(items) {
|
|
674
705
|
const counts = { complete: 0, action: 0, later: 0, info: 0 };
|
|
675
706
|
for (const current of items) counts[current.status] = (counts[current.status] || 0) + 1;
|
package/src/setup.js
CHANGED
|
@@ -19,6 +19,7 @@ export async function setupWorkspace(input = process.cwd(), payload = {}) {
|
|
|
19
19
|
draft: setup.draft,
|
|
20
20
|
system: plan.system,
|
|
21
21
|
workspace: plan.workspace,
|
|
22
|
+
renderer: plan.renderer,
|
|
22
23
|
linkedControlIds: [],
|
|
23
24
|
evidenceTestDraftIds: [],
|
|
24
25
|
onboardingComplete: !setup.draft
|
|
@@ -43,6 +44,7 @@ export async function planWorkspaceSetup(input = process.cwd(), payload = {}) {
|
|
|
43
44
|
},
|
|
44
45
|
system: setupSystemSummary(plan.system),
|
|
45
46
|
target: setupTargetSummary(plan.workspace),
|
|
47
|
+
renderer: plan.renderer ? setupRendererSummary(plan.renderer) : null,
|
|
46
48
|
onboardingComplete: !setup.draft
|
|
47
49
|
};
|
|
48
50
|
}
|
|
@@ -60,6 +62,7 @@ export function summarizeSetupResult(result) {
|
|
|
60
62
|
},
|
|
61
63
|
system: setupSystemSummary(result.system),
|
|
62
64
|
target: setupTargetSummary(result.workspace),
|
|
65
|
+
renderer: result.renderer ? setupRendererSummary(result.renderer) : null,
|
|
63
66
|
onboardingComplete: result.onboardingComplete
|
|
64
67
|
};
|
|
65
68
|
}
|
|
@@ -177,17 +180,13 @@ function assuranceGoalFromSetup(goal) {
|
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
function setupSystemSummary(system) {
|
|
180
|
-
return {
|
|
181
|
-
id: system.id,
|
|
182
|
-
title: system.title,
|
|
183
|
-
status: system.status,
|
|
184
|
-
inScope: system.inScope
|
|
185
|
-
};
|
|
183
|
+
return { ...system };
|
|
186
184
|
}
|
|
187
185
|
|
|
188
186
|
function setupTargetSummary(workspace) {
|
|
189
187
|
return {
|
|
190
188
|
assuranceGoal: workspace.assuranceGoal,
|
|
189
|
+
systemIds: [...(workspace.systemIds || [])],
|
|
191
190
|
scopeCounts: {
|
|
192
191
|
systems: workspace.systemIds?.length || 0,
|
|
193
192
|
frameworks: workspace.frameworkIds?.length || 0,
|
|
@@ -197,6 +196,14 @@ function setupTargetSummary(workspace) {
|
|
|
197
196
|
};
|
|
198
197
|
}
|
|
199
198
|
|
|
199
|
+
function setupRendererSummary(renderer) {
|
|
200
|
+
return {
|
|
201
|
+
id: renderer.id,
|
|
202
|
+
type: renderer.type,
|
|
203
|
+
showOnboarding: renderer.showOnboarding
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
200
207
|
function booleanValue(value, name) {
|
|
201
208
|
if (value === true || value === false) return value;
|
|
202
209
|
if (value === "true") return true;
|
package/src/web.js
CHANGED
|
@@ -1519,7 +1519,7 @@ function renderOnboardingStep() {
|
|
|
1519
1519
|
const finalActions = onboardingStep === steps.length - 1
|
|
1520
1520
|
? '<button class="button" type="button" data-onboarding="draft">Save draft</button><button class="button primary" type="button" data-onboarding="next">Complete setup</button>'
|
|
1521
1521
|
: '<button class="button primary" type="button" data-onboarding="next">Next</button>';
|
|
1522
|
-
onboardingDialog.innerHTML = '<div class="onboarding-progress" style="--onboarding-step-count:' + steps.length + '" aria-label="Onboarding step ' + (onboardingStep + 1) + ' of ' + steps.length + '">' + progress + '</div><div class="onboarding-head"><p class="kicker">' + esc(step.kicker) + ' · ' + (onboardingStep + 1) + ' of ' + steps.length + '</p><h2 id="onboarding-title">' + esc(titleCase(step.title)) + '</h2></div>' + body + '<div class="dialog-error" role="alert"></div><div class="dialog-actions onboarding-actions"><button class="button text-button onboarding-skip" type="button" data-onboarding="skip">Skip onboarding</button>' + (onboardingStep ? '<button class="button" type="button" data-onboarding="back">Back</button>' : "") + finalActions + '</div>';
|
|
1522
|
+
onboardingDialog.innerHTML = '<div class="onboarding-progress" style="--onboarding-step-count:' + steps.length + '" aria-label="Onboarding step ' + (onboardingStep + 1) + ' of ' + steps.length + '">' + progress + '</div><div class="onboarding-scroll"><div class="onboarding-head"><p class="kicker">' + esc(step.kicker) + ' · ' + (onboardingStep + 1) + ' of ' + steps.length + '</p><h2 id="onboarding-title">' + esc(titleCase(step.title)) + '</h2></div>' + body + '<div class="dialog-error" role="alert"></div></div><div class="dialog-actions onboarding-actions"><button class="button text-button onboarding-skip" type="button" data-onboarding="skip">Skip onboarding</button>' + (onboardingStep ? '<button class="button" type="button" data-onboarding="back">Back</button>' : "") + finalActions + '</div>';
|
|
1523
1523
|
onboardingDialog.querySelector('[data-onboarding="skip"]').addEventListener("click", cancelOnboarding);
|
|
1524
1524
|
onboardingDialog.querySelector('[data-onboarding="back"]')?.addEventListener("click", () => {
|
|
1525
1525
|
captureOnboardingForm();
|
|
@@ -1569,7 +1569,7 @@ function onboardingSetupForm() {
|
|
|
1569
1569
|
: state.git.available
|
|
1570
1570
|
? '<div class="onboarding-git-status warning"><span class="status-dot warn"></span><span><strong>Git remote needed</strong><small>Saving and local commits still work. Add a remote before the browser can push.</small></span></div>'
|
|
1571
1571
|
: '<div class="onboarding-git-status warning"><span class="status-dot warn"></span><span><strong>Git setup needed</strong><small>Saving still works. Run <code>git init</code> at the workspace root before your first compliance commit.</small></span></div>';
|
|
1572
|
-
return '<p class="onboarding-body">' + esc(onboardingSteps().at(-1).body) + '</p>' + gitStatus + '<form id="onboarding-setup" class="onboarding-form"><label class="wide"><span>Service name</span><input name="serviceName" required maxlength="200" value="' + esc(onboardingDraft.serviceName) + '" placeholder="Customer-facing application"></label><label class="wide"><span>Scope description</span><textarea name="scope" required maxlength="2000" placeholder="What the service does and which production boundary is in scope">' + esc(onboardingDraft.scope) + '</textarea></label><label><span>Accountable owner</span><select name="ownerId" required><option value="">Select</option>' + people.map(({ record }) => '<option value="' + esc(record.id) + '" ' + (record.id === onboardingDraft.ownerId ? "selected" : "") + '>' + esc(record.title) + '</option>').join("") + '</select></label><label><span>Business criticality</span><select name="criticality" required>' + ["low", "medium", "high", "critical"].map((value) => '<option value="' + value + '" ' + (value === onboardingDraft.criticality ? "selected" : "") + '>' + esc(properCase(value)) + '</option>').join("") + '</select></label><label><span>Highest data classification</span><select name="dataClassification" required>' + classifications.map((value) => '<option value="' + esc(value) + '" ' + (value === onboardingDraft.dataClassification ? "selected" : "") + '>' + esc(properCase(value)) + '</option>').join("") + '</select></label><label><span>Internet exposed</span><select name="internetExposed" required><option value="true" ' + (onboardingDraft.internetExposed === "true" ? "selected" : "") + '>Yes</option><option value="false" ' + (onboardingDraft.internetExposed === "false" ? "selected" : "") + '>No</option></select></label><label class="wide"><span>Program goal</span><select name="programGoal" required><option value="none" ' + (onboardingDraft.programGoal === "none" ? "selected" : "") + '>No Assurance Goal Yet</option><option value="readiness" ' + (onboardingDraft.programGoal === "readiness" ? "selected" : "") + '>Program Readiness</option><option value="type-1" ' + (onboardingDraft.programGoal === "type-1" ? "selected" : "") + '>SOC 2 Type 1</option><option value="type-2" ' + (onboardingDraft.programGoal === "type-2" ? "selected" : "") + '>SOC 2 Type 2</option></select><small>This records management intent only. It does not create an engagement or establish the formal report period.</small></label></form><p class="onboarding-write-note">' + esc(existing) + ' Save draft
|
|
1572
|
+
return '<p class="onboarding-body">' + esc(onboardingSteps().at(-1).body) + '</p>' + gitStatus + '<form id="onboarding-setup" class="onboarding-form"><label class="wide"><span>Service name</span><input name="serviceName" required maxlength="200" value="' + esc(onboardingDraft.serviceName) + '" placeholder="Customer-facing application"></label><label class="wide"><span>Scope description</span><textarea name="scope" required maxlength="2000" placeholder="What the service does and which production boundary is in scope">' + esc(onboardingDraft.scope) + '</textarea></label><label><span>Accountable owner</span><select name="ownerId" required><option value="">Select</option>' + people.map(({ record }) => '<option value="' + esc(record.id) + '" ' + (record.id === onboardingDraft.ownerId ? "selected" : "") + '>' + esc(record.title) + '</option>').join("") + '</select></label><label><span>Business criticality</span><select name="criticality" required>' + ["low", "medium", "high", "critical"].map((value) => '<option value="' + value + '" ' + (value === onboardingDraft.criticality ? "selected" : "") + '>' + esc(properCase(value)) + '</option>').join("") + '</select></label><label><span>Highest data classification</span><select name="dataClassification" required>' + classifications.map((value) => '<option value="' + esc(value) + '" ' + (value === onboardingDraft.dataClassification ? "selected" : "") + '>' + esc(properCase(value)) + '</option>').join("") + '</select></label><label><span>Internet exposed</span><select name="internetExposed" required><option value="true" ' + (onboardingDraft.internetExposed === "true" ? "selected" : "") + '>Yes</option><option value="false" ' + (onboardingDraft.internetExposed === "false" ? "selected" : "") + '>No</option></select></label><label class="wide"><span>Program goal</span><select name="programGoal" required><option value="none" ' + (onboardingDraft.programGoal === "none" ? "selected" : "") + '>No Assurance Goal Yet</option><option value="readiness" ' + (onboardingDraft.programGoal === "readiness" ? "selected" : "") + '>Program Readiness</option><option value="type-1" ' + (onboardingDraft.programGoal === "type-1" ? "selected" : "") + '>SOC 2 Type 1</option><option value="type-2" ' + (onboardingDraft.programGoal === "type-2" ? "selected" : "") + '>SOC 2 Type 2</option></select><small>This records management intent only. It does not create an engagement or establish the formal report period.</small></label></form><p class="onboarding-write-note">' + esc(existing) + ' Save draft marks the service Planned and In scope. It is selected for scope review, but it is not approved or active. Saving writes JSON files but does not commit them. Complete the remaining Step 1 pages next.</p>';
|
|
1573
1573
|
}
|
|
1574
1574
|
|
|
1575
1575
|
function captureOnboardingForm() {
|
|
@@ -2598,12 +2598,12 @@ html,body{height:100%;overflow:hidden}.shell{grid-template-columns:248px minmax(
|
|
|
2598
2598
|
.readiness-map{margin:14px 0;background:var(--panel);border:1px solid var(--line);border-radius:11px;padding:20px 22px;box-shadow:0 2px 8px rgba(21,40,33,.025)}.readiness-map-head{display:grid;grid-template-columns:minmax(220px,1fr) minmax(320px,420px);gap:28px;align-items:center;margin-bottom:17px}.readiness-map-head h3{font-size:18px;margin:5px 0 0}.readiness-progress-summary{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px 14px;align-items:center}.readiness-progress-summary>div{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:3px 12px;align-items:baseline}.readiness-progress-summary>div>span{color:var(--muted);font-size:9.6px;font-weight:700;text-transform:uppercase;letter-spacing:.08em}.readiness-progress-summary>div>strong{font-size:9.6px;font-weight:700;line-height:1.2}.readiness-progress-summary .progress,.readiness-progress-summary small{grid-column:1/-1}.readiness-progress-summary small{color:var(--muted);font-size:9.6px}.readiness-progress-summary>.button{white-space:nowrap}.readiness-flow{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px}.readiness-flow a{display:grid;grid-template-columns:23px minmax(0,1fr);column-gap:8px;align-content:start;min-width:0;padding:11px;border:1px solid var(--line);border-radius:8px;background:var(--surface-soft);text-decoration:none}.readiness-flow a:hover{border-color:var(--accent-light);background:var(--accent-soft)}.readiness-flow a>span{grid-row:1/4;display:grid;place-items:center;width:23px;height:23px;border-radius:50%;background:var(--primary-gradient);color:#fff;font-size:9.6px;font-weight:800}.readiness-flow strong{font-size:12px;line-height:1.25}.readiness-flow small{grid-column:2;color:var(--muted);font-size:9.6px;line-height:1.4;margin-top:3px}.readiness-state{grid-column:2;justify-self:start;margin-top:8px;padding:3px 6px;border-radius:99px;background:var(--surface-muted);color:var(--muted);font-size:8.4px;line-height:1.2}.readiness-state.good{background:#dcefe4;color:#125733}.readiness-state.warn{background:#f6e8c9;color:#79500f}.readiness-state.bad{background:#f7dfdc;color:#873027}.audit-engagement{display:grid;grid-template-columns:minmax(210px,1fr) minmax(260px,1.25fr) auto;gap:20px;align-items:center;padding:14px 15px;border-radius:8px;background:var(--surface-soft)}.audit-engagement strong{font-size:13.2px}.audit-engagement p,.audit-engagement li{color:var(--muted);font-size:10.8px;line-height:1.5}.audit-engagement p{margin:5px 0 0}.audit-engagement ul{margin:0;padding-left:18px}.audit-engagement .button{white-space:nowrap;text-decoration:none}.resource-directory{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.resource-directory>section{min-width:0;padding:12px;border-radius:8px;background:var(--surface-soft)}.resource-directory h4{margin:0 0 7px;color:var(--muted);font-size:10.8px;text-transform:uppercase;letter-spacing:.08em}.resource-directory a{display:flex;justify-content:space-between;gap:10px;padding:5px 0;border-top:1px solid var(--line);font-size:10.8px;text-decoration:none}.resource-directory a:first-of-type{border-top:0}.resource-directory a:hover span{color:var(--accent)}.resource-directory a strong{color:var(--muted);font-size:9.6px}.record-prose{max-width:790px}.record-prose section{padding:0 0 20px}.record-prose section+section{padding-top:20px;border-top:1px solid var(--line)}.record-prose h3{margin:0 0 7px;color:var(--muted);font-size:10.8px;text-transform:uppercase;letter-spacing:.08em}.record-prose p{margin:0;font-size:16.8px;line-height:1.65;white-space:pre-wrap}.connections-panel .panel-head>span{display:grid;place-items:center;min-width:22px;height:22px;border-radius:99px;background:var(--surface-muted);color:var(--muted);font-size:9.6px}.connections{display:grid}.connections a{display:block;padding:9px 0;border-top:1px solid var(--line);text-decoration:none}.connections a:first-child{padding-top:0;border-top:0}.connections strong,.connections small{display:block}.connections strong{font-size:12px}.connections small{margin-top:3px;color:var(--muted);font-size:9.6px;line-height:1.4}.connections a:hover strong{color:var(--accent)}.connections-more{margin:9px 0 0;color:var(--muted);font-size:9.6px;line-height:1.4}.external-source{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;color:var(--accent);text-decoration:none}.external-source span,.external-source strong,.external-source small{display:block}.external-source strong{font-size:12px;line-height:1.35}.external-source small{margin-top:3px;color:var(--muted);font-size:9.6px;line-height:1.35;overflow-wrap:anywhere}.external-source b{font-size:13.2px}.external-source:hover strong{text-decoration:underline}
|
|
2599
2599
|
.page-title-line{display:flex;align-items:center;gap:8px}.guide-trigger{display:grid;place-items:center;width:24px;height:24px;flex:0 0 auto;padding:0;border:1px solid var(--line);border-radius:50%;background:var(--panel);color:var(--muted);cursor:pointer}.guide-trigger svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round}.guide-trigger:hover{border-color:var(--accent-light);color:var(--accent)}.guide-trigger:focus-visible{outline:2px solid var(--focus);outline-offset:2px}.page-guide{display:grid;grid-template-columns:1.05fr 1.25fr 1fr;gap:0;margin:0;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:0 2px 8px rgba(21,40,33,.025)}.resource-guide-popover{position:fixed;z-index:40;overflow:auto;box-shadow:0 18px 50px rgba(0,0,24,.24)}.resource-guide-popover[hidden]{display:none}.page-guide>div{padding:14px 16px;border-left:1px solid var(--line);min-width:0}.page-guide>div:first-child{border-left:0}.page-guide>div>span{display:block;color:var(--accent);text-transform:uppercase;letter-spacing:.09em;font-size:9.6px;font-weight:780;margin-bottom:6px}.page-guide p{color:var(--muted);font-size:12px;line-height:1.5;margin:0}.guide-links{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}.guide-links a{color:var(--accent);background:var(--accent-soft);border-radius:99px;padding:4px 7px;text-decoration:none;font-size:9.6px;font-weight:700}
|
|
2600
2600
|
.operation-tracking{display:grid;gap:2px;min-width:0;text-decoration:none}.operation-tracking strong,.operation-tracking small{display:block;overflow-wrap:anywhere}.operation-tracking small{color:var(--muted);line-height:1.35}.operation-tracking.running strong{color:#176143}.operation-tracking.waiting strong,.operation-tracking.mixed strong{color:var(--amber)}.operation-tracking.paused strong{color:var(--red)}a.operation-tracking:hover strong{text-decoration:underline}
|
|
2601
|
-
.page-actions{display:flex;align-items:center;justify-content:flex-end;gap:7px;flex-wrap:wrap}.repository-sync-status{min-height:18px;margin:7px 0 0;color:var(--muted);font-size:13.2px}.repository-sync-status.error{color:var(--red)}.onboarding-dialog{width:min(470px,calc(100vw - 30px));max-height:calc(100vh - 32px);margin:0;border:1px solid var(--line);border-radius:13px;padding:0;background:var(--panel);color:var(--ink);box-shadow:0 28px 90px rgba(0,0,24,.38);overflow:
|
|
2601
|
+
.page-actions{display:flex;align-items:center;justify-content:flex-end;gap:7px;flex-wrap:wrap}.repository-sync-status{min-height:18px;margin:7px 0 0;color:var(--muted);font-size:13.2px}.repository-sync-status.error{color:var(--red)}.onboarding-dialog{width:min(470px,calc(100vw - 30px));max-height:calc(100vh - 32px);margin:0;border:1px solid var(--line);border-radius:13px;padding:0;background:var(--panel);color:var(--ink);box-shadow:0 28px 90px rgba(0,0,24,.38);overflow:hidden}.onboarding-dialog[open]{display:flex;flex-direction:column}.onboarding-dialog::backdrop{background:transparent;backdrop-filter:none}.onboarding-shade{position:fixed;inset:0;z-index:60;pointer-events:none}.onboarding-shade span{position:absolute;background:rgba(0,0,24,.58)}.onboarding-progress{display:grid;flex:0 0 auto;grid-template-columns:repeat(var(--onboarding-step-count),1fr);gap:5px;padding:18px 24px 0}.onboarding-progress span{height:3px;border-radius:3px;background:var(--surface-muted)}.onboarding-progress span.active{background:var(--accent-light)}.onboarding-scroll{min-height:0;overflow-y:auto}.onboarding-head{padding:22px 25px 0}.onboarding-head h2{font-family:Georgia,serif;font-size:30px;font-weight:500;letter-spacing:-.015em;margin:8px 0 0}.onboarding-body{color:var(--muted);font-size:14.4px;line-height:1.6;margin:13px 25px 0}.onboarding-body+.onboarding-body{margin-top:8px}.onboarding-points{display:grid;gap:9px;margin:18px 25px 4px;padding-left:19px}.onboarding-points li{font-size:13.2px;line-height:1.5;padding-left:3px}.onboarding-sections{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 25px 4px}.onboarding-sections section{padding:13px;border:1px solid var(--line);border-radius:8px;background:var(--surface-soft)}.onboarding-sections strong{font-size:13.2px}.onboarding-sections p{margin:6px 0 0;color:var(--muted);font-size:12px;line-height:1.5}.onboarding-actions{flex:0 0 auto;padding:12px 25px 23px;border-top:1px solid var(--line);background:var(--panel)}.onboarding-skip{margin-right:auto;color:var(--muted);text-transform:none;letter-spacing:0;font-size:13.2px}.onboarding-form{display:grid;grid-template-columns:1fr 1fr;gap:13px;margin:18px 25px 0}.onboarding-form label{display:block;min-width:0}.onboarding-form label.wide{grid-column:1/-1}.onboarding-form label>span{display:block;color:var(--ink);font-size:12px;font-weight:720;margin-bottom:6px}.onboarding-form input,.onboarding-form select,.onboarding-form textarea{width:100%;min-height:40px;border:1px solid var(--line);border-radius:7px;background:var(--field);color:var(--ink);padding:9px 10px;font-size:14.4px}.onboarding-form textarea{min-height:78px;resize:vertical}.onboarding-form small{display:block;color:var(--muted);font-size:10.8px;line-height:1.45;margin-top:5px}.onboarding-write-note{color:var(--muted);font-size:10.8px;line-height:1.5;margin:12px 25px 16px}.onboarding-scroll>.dialog-error{margin:8px 25px 0}.onboarding-focus{outline:4px solid var(--accent-light)!important;outline-offset:5px;scroll-margin-top:102px}
|
|
2602
2602
|
.page-intro,.detail-head{align-items:center;margin-bottom:12px}.actions{align-items:center}.detail-head>div:first-child{min-width:0}.detail-head h2{margin:7px 0}.detail-head .header-breadcrumbs{margin:0;font-size:10.8px;line-height:normal;min-height:11px;align-items:center}.header-breadcrumbs span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60ch}
|
|
2603
2603
|
@media(max-width:1200px){.readiness-flow{grid-template-columns:repeat(3,minmax(0,1fr))}.audit-engagement{grid-template-columns:1fr 1fr}.audit-engagement .button{grid-column:1/-1;justify-self:start}}
|
|
2604
2604
|
@media(max-width:1100px){.search{display:none}.topbar-status{margin-left:auto}.metrics{grid-template-columns:repeat(2,1fr)}.dashboard-grid,.organization-grid{grid-template-columns:repeat(2,1fr)}.catalog{grid-template-columns:repeat(3,1fr)}.span-2{grid-column:span 2}.resource-directory{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
2605
2605
|
@media(max-width:760px){.shell{display:block}.sidebar{transform:translateX(-100%);transition:.2s;box-shadow:8px 0 30px rgba(0,0,0,.2)}.sidebar.shown{transform:translateX(0)}.workspace{min-width:0}.mobile-nav{display:block;border:0;background:none;font-size:24px}.topbar{height:72px;padding:0 16px}.topbar>div:first-of-type{min-width:0}.topbar-status{display:none}.search{display:flex;max-width:none}.search kbd,.topbar .eyebrow{display:none}.page{padding:20px 15px 60px}.hero{display:block;padding:23px}.hero-meta{margin-top:22px;flex-wrap:wrap}.metrics,.dashboard-grid,.organization-grid{grid-template-columns:1fr}.span-2{grid-column:auto}.catalog{grid-template-columns:repeat(2,1fr)}.detail-grid{grid-template-columns:1fr}.page-intro,.detail-head{display:block}.page-intro>.button,.actions{margin-top:15px}.page-intro>.list-header-tools{justify-content:flex-start;margin:15px 0 0}.list-header-tools label{max-width:none}.record-table{min-width:720px}.readiness-map{padding:17px}.readiness-map-head{grid-template-columns:1fr;gap:8px}.readiness-flow{grid-template-columns:repeat(2,minmax(0,1fr))}.audit-engagement{grid-template-columns:1fr}.audit-engagement .button{grid-column:auto}.resource-directory{grid-template-columns:1fr}}
|
|
2606
|
-
@media(max-width:760px){.setup-banner,.page-guide,.stage-overview-hero,.relationship-note,.group-destination-card,.stage-page-grid{grid-template-columns:1fr}.page-guide>div{border-left:0;border-top:1px solid var(--line)}.page-guide>div:first-child{border-top:0}.group-overview-head{display:block}.stage-progress-card,.stage-status-link{margin-top:15px}.destination-rollup{text-align:left}.form-grid{grid-template-columns:1fr}.record-table{min-width:0}.record-table thead{display:none}.record-table,.record-table tbody,.record-table tr{display:block}.record-table tr{padding:8px 12px;border-bottom:1px solid var(--line)}.record-table tr:last-child{border-bottom:0}.record-table td:not([data-label]){display:block}.record-table td[data-label]{display:grid;grid-template-columns:105px minmax(0,1fr);gap:10px;border:0;padding:7px 0;align-items:start}.record-table td[data-label]::before{content:attr(data-label);color:#75817b;text-transform:uppercase;letter-spacing:.07em;font-size:9.6px;font-weight:700}.record-table td[data-primary-field]{display:block;padding:8px 0 10px}.record-table td[data-primary-field]::before{display:none}.content-label{align-items:flex-start}.editor form{padding:18px}.diagnostics>div{grid-template-columns:58px minmax(0,1fr)}.diagnostics p{grid-column:1/-1}.changes code{overflow-wrap:anywhere}.onboarding-dialog{max-height:56vh}
|
|
2606
|
+
@media(max-width:760px){.setup-banner,.page-guide,.stage-overview-hero,.relationship-note,.group-destination-card,.stage-page-grid{grid-template-columns:1fr}.page-guide>div{border-left:0;border-top:1px solid var(--line)}.page-guide>div:first-child{border-top:0}.group-overview-head{display:block}.stage-progress-card,.stage-status-link{margin-top:15px}.destination-rollup{text-align:left}.form-grid{grid-template-columns:1fr}.record-table{min-width:0}.record-table thead{display:none}.record-table,.record-table tbody,.record-table tr{display:block}.record-table tr{padding:8px 12px;border-bottom:1px solid var(--line)}.record-table tr:last-child{border-bottom:0}.record-table td:not([data-label]){display:block}.record-table td[data-label]{display:grid;grid-template-columns:105px minmax(0,1fr);gap:10px;border:0;padding:7px 0;align-items:start}.record-table td[data-label]::before{content:attr(data-label);color:#75817b;text-transform:uppercase;letter-spacing:.07em;font-size:9.6px;font-weight:700}.record-table td[data-primary-field]{display:block;padding:8px 0 10px}.record-table td[data-primary-field]::before{display:none}.content-label{align-items:flex-start}.editor form{padding:18px}.diagnostics>div{grid-template-columns:58px minmax(0,1fr)}.diagnostics p{grid-column:1/-1}.changes code{overflow-wrap:anywhere}.onboarding-dialog{max-height:56vh}}
|
|
2607
2607
|
@media(max-width:520px){.onboarding-form,.onboarding-sections,.setup-steps{grid-template-columns:1fr}.onboarding-form label.wide{grid-column:auto}.onboarding-actions{flex-wrap:wrap}.onboarding-skip{width:100%;order:3;margin:3px 0 0}.readiness-flow{grid-template-columns:1fr}.obligation-card-foot{align-items:flex-start;flex-direction:column}.obligation-action{align-self:flex-start}}
|
|
2608
2608
|
@media(min-width:761px){.detail-grid{grid-template-columns:minmax(270px,1fr) minmax(0,2fr)}.detail-grid aside{grid-column:1;grid-row:1}.detail-main{grid-column:2;grid-row:1}}
|
|
2609
2609
|
@media(min-width:761px){.detail-grid.detail-grid-structured{grid-template-columns:1fr}.detail-grid-structured aside{grid-column:1;grid-row:1;grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}.detail-grid-structured aside>.panel{align-self:start}}
|