@kontextmind/kxm 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +19 -0
- package/.kxm/README.md +14 -0
- package/.kxm/assets/README.md +5 -0
- package/.kxm/assets/retrospectives/README.md +5 -0
- package/.kxm/config/README.md +5 -0
- package/.kxm/config/agents.json +43 -0
- package/.kxm/config/env.example +56 -0
- package/.kxm/config/update.example.yaml +9 -0
- package/.kxm/config/workflows/fix.json +160 -0
- package/.kxm/config/workflows/jira-development.json +116 -0
- package/.kxm/config/workflows/provenance-quorum.json +150 -0
- package/.kxm/config/workflows/v04-dogfood.json +72 -0
- package/CHANGELOG.md +465 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/SECURITY.md +72 -0
- package/docs/README.md +48 -0
- package/docs/agent-communication-envelopes-and-gates.md +553 -0
- package/docs/architecture.md +242 -0
- package/docs/assignment-runner.md +241 -0
- package/docs/configuration.md +361 -0
- package/docs/continuous-improvement.md +114 -0
- package/docs/getting-started.md +253 -0
- package/docs/kxm-handbook.md +1090 -0
- package/docs/operations.md +205 -0
- package/docs/provenance-gates.md +291 -0
- package/docs/skills.md +45 -0
- package/docs/templates/README.md +95 -0
- package/docs/templates/adr.md +88 -0
- package/docs/templates/architecture.md +120 -0
- package/docs/templates/bug-fix.md +109 -0
- package/docs/templates/feature.md +108 -0
- package/docs/templates/handoff.md +72 -0
- package/docs/templates/postmortem.md +77 -0
- package/docs/templates/research.md +100 -0
- package/docs/templates/review.md +85 -0
- package/docs/templates/runbook.md +73 -0
- package/docs/templates/test-plan.md +87 -0
- package/docs/templates/test-report.md +72 -0
- package/docs/test-matrix.md +121 -0
- package/docs/troubleshooting.md +249 -0
- package/docs/vnext/README.md +62 -0
- package/docs/vnext/architecture.md +185 -0
- package/docs/vnext/effects-and-recovery.md +172 -0
- package/docs/vnext/lifecycles.md +235 -0
- package/docs/vnext/migration.md +220 -0
- package/docs/vnext/routing.md +184 -0
- package/docs/vnext/synchronization.md +172 -0
- package/docs/vnext/terminology.md +240 -0
- package/docs/vnext/validation.md +335 -0
- package/docs/webhook-workflows.md +240 -0
- package/docs/workflow-guide.md +1150 -0
- package/examples/README.md +102 -0
- package/examples/provenance-workflow.json +40 -0
- package/examples/requester.ts +30 -0
- package/examples/reviewer-agent.ts +29 -0
- package/examples/roundtrip.ts +46 -0
- package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
- package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
- package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
- package/examples/vnext/.kxm/agents/planner.yaml +13 -0
- package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
- package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
- package/examples/vnext/.kxm/gates.yaml +8 -0
- package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
- package/examples/vnext/.kxm/models/implementation.yaml +14 -0
- package/examples/vnext/.kxm/models/primary.yaml +17 -0
- package/examples/vnext/.kxm/prices.yaml +111 -0
- package/examples/vnext/.kxm/project/env.yaml +7 -0
- package/examples/vnext/.kxm/project.yaml +32 -0
- package/examples/vnext/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/.kxm/workflows/default.yaml +92 -0
- package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
- package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
- package/examples/vnext/README.md +53 -0
- package/examples/vnext/records/assignment-result-recorded.json +63 -0
- package/examples/vnext/records/assignment-result.json +46 -0
- package/examples/vnext/records/context-candidate.json +42 -0
- package/examples/vnext/records/delivery-manifest.json +66 -0
- package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
- package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
- package/examples/vnext/records/run-created.json +54 -0
- package/examples/vnext/records/sync-event.json +65 -0
- package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
- package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
- package/examples/workflow-signal.ts +63 -0
- package/package.json +129 -0
- package/plugins/kxm/.claude-plugin/plugin.json +73 -0
- package/plugins/kxm/.mcp.json +19 -0
- package/plugins/kxm/README.md +93 -0
- package/plugins/kxm/dist/cli.js +42853 -0
- package/plugins/kxm/dist/client.js +416 -0
- package/plugins/kxm/dist/core.js +1823 -0
- package/plugins/kxm/dist/extension.js +3797 -0
- package/plugins/kxm/dist/mcp-server.js +17104 -0
- package/plugins/kxm/dist/runtime.js +23361 -0
- package/plugins/kxm/dist/server.js +13640 -0
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
- package/plugins/kxm/package.json +12 -0
- package/plugins/kxm/skills/kxm/SKILL.md +97 -0
- package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
- package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
- package/plugins/kxm/src/arbiter.ts +355 -0
- package/plugins/kxm/src/artifacts-exist.ts +62 -0
- package/plugins/kxm/src/autocomplete.ts +236 -0
- package/plugins/kxm/src/cli.ts +3707 -0
- package/plugins/kxm/src/client.ts +614 -0
- package/plugins/kxm/src/commands.ts +1063 -0
- package/plugins/kxm/src/config.ts +290 -0
- package/plugins/kxm/src/context/providers.ts +101 -0
- package/plugins/kxm/src/context-packet.ts +332 -0
- package/plugins/kxm/src/context.ts +499 -0
- package/plugins/kxm/src/core.ts +6 -0
- package/plugins/kxm/src/database.ts +563 -0
- package/plugins/kxm/src/diagnostics.ts +184 -0
- package/plugins/kxm/src/envelope.ts +118 -0
- package/plugins/kxm/src/extension.ts +895 -0
- package/plugins/kxm/src/external-effects.ts +299 -0
- package/plugins/kxm/src/github-watch.ts +255 -0
- package/plugins/kxm/src/hub-binding.ts +160 -0
- package/plugins/kxm/src/hub.ts +2502 -0
- package/plugins/kxm/src/improve.ts +383 -0
- package/plugins/kxm/src/inbox.ts +10 -0
- package/plugins/kxm/src/kxm-install-kind.ts +113 -0
- package/plugins/kxm/src/kxm-update-config.ts +39 -0
- package/plugins/kxm/src/kxm-update.ts +238 -0
- package/plugins/kxm/src/local-snapshot.ts +406 -0
- package/plugins/kxm/src/logger.ts +198 -0
- package/plugins/kxm/src/mcp-server.ts +143 -0
- package/plugins/kxm/src/memory.ts +385 -0
- package/plugins/kxm/src/nous-pi.ts +287 -0
- package/plugins/kxm/src/nous-provider.ts +729 -0
- package/plugins/kxm/src/price-calc.ts +87 -0
- package/plugins/kxm/src/prices.ts +121 -0
- package/plugins/kxm/src/protocol.ts +172 -0
- package/plugins/kxm/src/recovery.ts +211 -0
- package/plugins/kxm/src/redact.ts +26 -0
- package/plugins/kxm/src/retrospective.ts +400 -0
- package/plugins/kxm/src/routing.ts +830 -0
- package/plugins/kxm/src/runtime.ts +9 -0
- package/plugins/kxm/src/server.ts +117 -0
- package/plugins/kxm/src/session-work.ts +571 -0
- package/plugins/kxm/src/session.ts +184 -0
- package/plugins/kxm/src/skills.ts +535 -0
- package/plugins/kxm/src/state.ts +326 -0
- package/plugins/kxm/src/store.ts +637 -0
- package/plugins/kxm/src/studio-layout.ts +268 -0
- package/plugins/kxm/src/suggest.ts +162 -0
- package/plugins/kxm/src/task-manager.ts +244 -0
- package/plugins/kxm/src/telemetry.ts +116 -0
- package/plugins/kxm/src/tui.ts +1046 -0
- package/plugins/kxm/src/vnext-bindings.ts +403 -0
- package/plugins/kxm/src/vnext-config.ts +1646 -0
- package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
- package/plugins/kxm/src/vnext-engine-command.ts +533 -0
- package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
- package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
- package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
- package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
- package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
- package/plugins/kxm/src/vnext-engine.ts +2458 -0
- package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
- package/plugins/kxm/src/vnext-harness.ts +1142 -0
- package/plugins/kxm/src/vnext-init.ts +430 -0
- package/plugins/kxm/src/vnext-migrate.ts +1848 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
- package/plugins/kxm/src/vnext-permission.ts +936 -0
- package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
- package/plugins/kxm/src/vnext-repair.ts +1094 -0
- package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
- package/plugins/kxm/src/vnext-runtime.ts +663 -0
- package/plugins/kxm/src/vnext-template.ts +247 -0
- package/plugins/kxm/src/wiki.ts +313 -0
- package/plugins/kxm/src/workflow.ts +1548 -0
- package/schemas/vnext/README.md +46 -0
- package/schemas/vnext/agent.schema.json +40 -0
- package/schemas/vnext/assignment-result.schema.json +66 -0
- package/schemas/vnext/backup-manifest.schema.json +89 -0
- package/schemas/vnext/candidate.schema.json +109 -0
- package/schemas/vnext/common.schema.json +422 -0
- package/schemas/vnext/context-candidate.schema.json +76 -0
- package/schemas/vnext/context-packet.schema.json +192 -0
- package/schemas/vnext/delivery-manifest.schema.json +159 -0
- package/schemas/vnext/environment.schema.json +66 -0
- package/schemas/vnext/gate-registry.schema.json +109 -0
- package/schemas/vnext/handoff-manifest.schema.json +146 -0
- package/schemas/vnext/init-operation.schema.json +61 -0
- package/schemas/vnext/local-repository-bindings.schema.json +30 -0
- package/schemas/vnext/memory-record.schema.json +45 -0
- package/schemas/vnext/migration-decision.schema.json +26 -0
- package/schemas/vnext/migration-plan.schema.json +123 -0
- package/schemas/vnext/migration-receipt.schema.json +52 -0
- package/schemas/vnext/model.schema.json +42 -0
- package/schemas/vnext/permission-diff.schema.json +57 -0
- package/schemas/vnext/prices.schema.json +115 -0
- package/schemas/vnext/project.schema.json +85 -0
- package/schemas/vnext/repository.schema.json +24 -0
- package/schemas/vnext/run-event.schema.json +460 -0
- package/schemas/vnext/session-brief.schema.json +153 -0
- package/schemas/vnext/sync-event.schema.json +234 -0
- package/schemas/vnext/template-provenance.schema.json +38 -0
- package/schemas/vnext/workflow.schema.json +248 -0
- package/scripts/assignment-run.d.mts +354 -0
- package/scripts/assignment-run.mjs +4451 -0
- package/scripts/build-runtime.mjs +56 -0
- package/scripts/check-generated.mjs +77 -0
- package/scripts/check-versions.mjs +34 -0
- package/scripts/emit-codex-artifacts.d.mts +9 -0
- package/scripts/emit-codex-artifacts.mjs +91 -0
- package/scripts/harness-run.d.mts +83 -0
- package/scripts/harness-run.mjs +2095 -0
- package/scripts/kxm-hub.mjs +105 -0
- package/scripts/kxm-publish-npm.mjs +327 -0
- package/scripts/kxm-release-github.mjs +472 -0
- package/scripts/kxm-runtime-supervisor.mjs +7 -0
- package/scripts/kxm-worker.mjs +1127 -0
- package/scripts/kxm.mjs +27 -0
- package/scripts/roster-policy.d.mts +20 -0
- package/scripts/roster-policy.mjs +161 -0
- package/scripts/smoke-multi-pi.mjs +479 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { nowIso } from "./protocol.ts";
|
|
4
|
+
import { agentWorker, gateWorker, type Worker } from "./envelope.ts";
|
|
5
|
+
|
|
6
|
+
export const SESSION_SCHEMA = "kxm.session.v1" as const;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Roster/mix configuration is invalid: unknown worker names, malformed roster
|
|
10
|
+
* files, duplicate names, or kind/driver mismatches. Thrown fail-closed;
|
|
11
|
+
* callers (the CLI) are expected to map this to a usage exit, never to
|
|
12
|
+
* silently degrade into a default worker.
|
|
13
|
+
*/
|
|
14
|
+
export class SessionConfigError extends Error {
|
|
15
|
+
readonly code = "session_config_invalid" as const;
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "SessionConfigError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SessionRecord {
|
|
23
|
+
schema: typeof SESSION_SCHEMA;
|
|
24
|
+
id: string;
|
|
25
|
+
host: "local" | "hub";
|
|
26
|
+
mode: "mix" | "workflow";
|
|
27
|
+
workflowId?: string;
|
|
28
|
+
workers: Worker[];
|
|
29
|
+
createdAt: string;
|
|
30
|
+
assetDir: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function workflowAssetDirs(assetsDir: string, workflowId: string): string[] {
|
|
34
|
+
const root = join(assetsDir, "workflows", workflowId);
|
|
35
|
+
return [root, join(root, "inputs"), join(root, "outputs"), join(root, "generated")];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function sessionAssetDirs(assetsDir: string, sessionId: string): string[] {
|
|
39
|
+
const root = join(assetsDir, "sessions", sessionId);
|
|
40
|
+
return [root, join(root, "inputs"), join(root, "outputs")];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function standardAssetDirs(assetsDir: string): string[] {
|
|
44
|
+
return [
|
|
45
|
+
join(assetsDir, "retrospectives"),
|
|
46
|
+
join(assetsDir, "workflows"),
|
|
47
|
+
join(assetsDir, "sessions"),
|
|
48
|
+
join(assetsDir, "improvements"),
|
|
49
|
+
join(assetsDir, "generated"),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function rosterNames(configDir: string): string[] {
|
|
54
|
+
return [...loadRosterMap(configDir).values()].map((row) => String(row.name));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function loadNamedWorkers(configDir: string, names: string[], project?: string): Worker[] {
|
|
58
|
+
const byName = loadRosterMap(configDir);
|
|
59
|
+
return names.map((name) => {
|
|
60
|
+
const trimmed = name.trim();
|
|
61
|
+
const row = byName.get(trimmed.toLowerCase());
|
|
62
|
+
if (!row) {
|
|
63
|
+
// Fail closed: an unknown mix name is a configuration error, not a
|
|
64
|
+
// reason to mint a code gate with that name.
|
|
65
|
+
throw new SessionConfigError(
|
|
66
|
+
`unknown worker name in session roster: ${trimmed} (not present in agents.json or gates.json)`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return workerFromRosterRow(trimmed, row, project);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function workerFromRosterRow(name: string, row: Record<string, unknown>, project?: string): Worker {
|
|
74
|
+
const kind = row.kind;
|
|
75
|
+
const driver = row.driver;
|
|
76
|
+
if (kind !== undefined && kind !== "agent" && kind !== "gate") {
|
|
77
|
+
throw new SessionConfigError(`roster entry ${name} has invalid kind: ${JSON.stringify(kind)}`);
|
|
78
|
+
}
|
|
79
|
+
if (driver !== undefined && driver !== "ai" && driver !== "code") {
|
|
80
|
+
throw new SessionConfigError(`roster entry ${name} has invalid driver: ${JSON.stringify(driver)}`);
|
|
81
|
+
}
|
|
82
|
+
if ((kind === "agent" && driver === "code") || (kind === "gate" && driver === "ai")) {
|
|
83
|
+
throw new SessionConfigError(
|
|
84
|
+
`roster entry ${name} has kind/driver mismatch: kind=${String(kind)} driver=${String(driver)}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (kind === "gate" || driver === "code") {
|
|
88
|
+
return gateWorker({
|
|
89
|
+
name,
|
|
90
|
+
...(project ? { project } : {}),
|
|
91
|
+
...(typeof row.purpose === "string" ? { purpose: row.purpose } : {}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return agentWorker({
|
|
95
|
+
name,
|
|
96
|
+
...(project ? { project } : {}),
|
|
97
|
+
...(typeof row.purpose === "string" ? { purpose: row.purpose } : {}),
|
|
98
|
+
...(typeof row.model === "string" ? { model: row.model } : {}),
|
|
99
|
+
...(typeof row.thinking === "string" ? { thinking: row.thinking } : {}),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function loadRosterMap(configDir: string): Map<string, Record<string, unknown>> {
|
|
104
|
+
const byName = new Map<string, Record<string, unknown>>();
|
|
105
|
+
for (const [file, key] of [["agents.json", "agents"], ["gates.json", "gates"]] as const) {
|
|
106
|
+
const path = join(configDir, file);
|
|
107
|
+
for (const row of loadRoster(path, key)) {
|
|
108
|
+
const name = String(row.name).trim();
|
|
109
|
+
const lowered = name.toLowerCase();
|
|
110
|
+
if (byName.has(lowered)) {
|
|
111
|
+
throw new SessionConfigError(`duplicate worker name ${name} across roster files (${file} and an earlier roster)`);
|
|
112
|
+
}
|
|
113
|
+
// Validate every configured row, not only names selected by one mix. A
|
|
114
|
+
// partial selection must not make a malformed sibling roster entry safe.
|
|
115
|
+
void workerFromRosterRow(name, row);
|
|
116
|
+
byName.set(lowered, row);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return byName;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function loadRoster(path: string, key: "agents" | "gates"): Array<Record<string, unknown>> {
|
|
123
|
+
// A missing roster file means "no workers of this kind configured"; a
|
|
124
|
+
// present-but-malformed file is a configuration error and must fail closed.
|
|
125
|
+
if (!existsSync(path)) return [];
|
|
126
|
+
let parsed: unknown;
|
|
127
|
+
try {
|
|
128
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new SessionConfigError(
|
|
131
|
+
`${key} roster at ${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
135
|
+
throw new SessionConfigError(`${key} roster at ${path} must be a JSON object`);
|
|
136
|
+
}
|
|
137
|
+
const list = (parsed as Record<string, unknown>)[key];
|
|
138
|
+
if (!Array.isArray(list)) {
|
|
139
|
+
throw new SessionConfigError(`${key} roster at ${path} must contain a "${key}" array`);
|
|
140
|
+
}
|
|
141
|
+
return list.map((row, index) => {
|
|
142
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
143
|
+
throw new SessionConfigError(`${key} roster entry ${index} at ${path} must be an object`);
|
|
144
|
+
}
|
|
145
|
+
const record = row as Record<string, unknown>;
|
|
146
|
+
if (typeof record.name !== "string" || !record.name.trim()) {
|
|
147
|
+
throw new SessionConfigError(`${key} roster entry ${index} at ${path} must have a non-empty name`);
|
|
148
|
+
}
|
|
149
|
+
return record;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function createSession(input: {
|
|
154
|
+
id: string;
|
|
155
|
+
host: "local" | "hub";
|
|
156
|
+
mode: "mix" | "workflow";
|
|
157
|
+
workflowId?: string;
|
|
158
|
+
workers: Worker[];
|
|
159
|
+
assetsDir: string;
|
|
160
|
+
}): SessionRecord {
|
|
161
|
+
const assetDir = input.mode === "workflow" && input.workflowId
|
|
162
|
+
? join("assets", "workflows", input.workflowId)
|
|
163
|
+
: join("assets", "sessions", input.id);
|
|
164
|
+
return {
|
|
165
|
+
schema: SESSION_SCHEMA,
|
|
166
|
+
id: input.id,
|
|
167
|
+
host: input.host,
|
|
168
|
+
mode: input.mode,
|
|
169
|
+
workers: input.workers,
|
|
170
|
+
createdAt: nowIso(),
|
|
171
|
+
assetDir,
|
|
172
|
+
...(input.workflowId ? { workflowId: input.workflowId } : {}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function writeSession(assetsDir: string, session: SessionRecord, dryRun = false): string {
|
|
177
|
+
const dir = join(assetsDir, "sessions", session.id);
|
|
178
|
+
const path = join(dir, "session.json");
|
|
179
|
+
if (!dryRun) {
|
|
180
|
+
mkdirSync(dir, { recursive: true });
|
|
181
|
+
writeFileSync(path, `${JSON.stringify(session, null, 2)}\n`, { encoding: "utf8" });
|
|
182
|
+
}
|
|
183
|
+
return path;
|
|
184
|
+
}
|